Commit cbe8813f authored by Raymond Hettinger's avatar Raymond Hettinger

Add locks to make the caches well behaved in multi-threaded code.

Store builtins in cell variables to speed-up the common path,
reducing the chance of a lock needing to block at all.
parent d9e8cc62
...@@ -15,6 +15,10 @@ from _functools import partial, reduce ...@@ -15,6 +15,10 @@ from _functools import partial, reduce
from collections import OrderedDict, Counter from collections import OrderedDict, Counter
from heapq import nsmallest from heapq import nsmallest
from operator import itemgetter from operator import itemgetter
try:
from _thread import allocate_lock as Lock
except:
from _dummy_thread import allocate_lock as Lock
# update_wrapper() and wraps() are tools to help write # update_wrapper() and wraps() are tools to help write
# wrapper functions that can handle naive introspection # wrapper functions that can handle naive introspection
...@@ -115,22 +119,26 @@ def lfu_cache(maxsize=100): ...@@ -115,22 +119,26 @@ def lfu_cache(maxsize=100):
http://en.wikipedia.org/wiki/Cache_algorithms#Least-Frequently_Used http://en.wikipedia.org/wiki/Cache_algorithms#Least-Frequently_Used
""" """
def decorating_function(user_function): def decorating_function(user_function, tuple=tuple, sorted=sorted, len=len):
cache = {} # mapping of args to results cache = {} # mapping of args to results
use_count = Counter() # times each key has been accessed use_count = Counter() # times each key has been accessed
kwd_mark = object() # separate positional and keyword args kwd_mark = object() # separate positional and keyword args
lock = Lock()
@wraps(user_function) @wraps(user_function)
def wrapper(*args, **kwds): def wrapper(*args, **kwds):
key = args key = args
if kwds: if kwds:
key += (kwd_mark,) + tuple(sorted(kwds.items())) key += (kwd_mark,) + tuple(sorted(kwds.items()))
use_count[key] += 1 # count a use of this key
try: try:
with lock:
use_count[key] += 1 # count a use of this key
result = cache[key] result = cache[key]
wrapper.hits += 1 wrapper.hits += 1
except KeyError: except KeyError:
result = user_function(*args, **kwds) result = user_function(*args, **kwds)
with lock:
use_count[key] += 1 # count a use of this key
cache[key] = result cache[key] = result
wrapper.misses += 1 wrapper.misses += 1
if len(cache) > maxsize: if len(cache) > maxsize:
...@@ -143,6 +151,7 @@ def lfu_cache(maxsize=100): ...@@ -143,6 +151,7 @@ def lfu_cache(maxsize=100):
def clear(): def clear():
"""Clear the cache and cache statistics""" """Clear the cache and cache statistics"""
with lock:
cache.clear() cache.clear()
use_count.clear() use_count.clear()
wrapper.hits = wrapper.misses = 0 wrapper.hits = wrapper.misses = 0
...@@ -161,9 +170,10 @@ def lru_cache(maxsize=100): ...@@ -161,9 +170,10 @@ def lru_cache(maxsize=100):
http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
""" """
def decorating_function(user_function): def decorating_function(user_function, tuple=tuple, sorted=sorted, len=len):
cache = OrderedDict() # ordered least recent to most recent cache = OrderedDict() # ordered least recent to most recent
kwd_mark = object() # separate positional and keyword args kwd_mark = object() # separate positional and keyword args
lock = Lock()
@wraps(user_function) @wraps(user_function)
def wrapper(*args, **kwds): def wrapper(*args, **kwds):
...@@ -171,18 +181,23 @@ def lru_cache(maxsize=100): ...@@ -171,18 +181,23 @@ def lru_cache(maxsize=100):
if kwds: if kwds:
key += (kwd_mark,) + tuple(sorted(kwds.items())) key += (kwd_mark,) + tuple(sorted(kwds.items()))
try: try:
result = cache.pop(key) with lock:
result = cache[key]
del cache[key]
cache[key] = result # record recent use of this key
wrapper.hits += 1 wrapper.hits += 1
except KeyError: except KeyError:
result = user_function(*args, **kwds) result = user_function(*args, **kwds)
with lock:
cache[key] = result # record recent use of this key
wrapper.misses += 1 wrapper.misses += 1
if len(cache) >= maxsize: if len(cache) > maxsize:
cache.popitem(0) # purge least recently used cache entry cache.popitem(0) # purge least recently used cache entry
cache[key] = result # record recent use of this key
return result return result
def clear(): def clear():
"""Clear the cache and cache statistics""" """Clear the cache and cache statistics"""
with lock:
cache.clear() cache.clear()
wrapper.hits = wrapper.misses = 0 wrapper.hits = wrapper.misses = 0
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment