summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorRaymond Hettinger <python@rcn.com>2010-08-14 22:22:10 (GMT)
committerRaymond Hettinger <python@rcn.com>2010-08-14 22:22:10 (GMT)
commitcbe8813f18da15b239c58e1ba5c236c77872e413 (patch)
tree463b344b616cb54da0fcd14f753840fd5062874c /Lib
parentd9e8cc6249c7ff4ceeff3217a7671bee623d88a7 (diff)
downloadcpython-cbe8813f18da15b239c58e1ba5c236c77872e413.zip
cpython-cbe8813f18da15b239c58e1ba5c236c77872e413.tar.gz
cpython-cbe8813f18da15b239c58e1ba5c236c77872e413.tar.bz2
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.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/functools.py63
1 files changed, 39 insertions, 24 deletions
diff --git a/Lib/functools.py b/Lib/functools.py
index 9d8cea9..815386b 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -15,6 +15,10 @@ from _functools import partial, reduce
from collections import OrderedDict, Counter
from heapq import nsmallest
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
# wrapper functions that can handle naive introspection
@@ -115,37 +119,42 @@ def lfu_cache(maxsize=100):
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
use_count = Counter() # times each key has been accessed
kwd_mark = object() # separate positional and keyword args
+ lock = Lock()
@wraps(user_function)
def wrapper(*args, **kwds):
key = args
if kwds:
key += (kwd_mark,) + tuple(sorted(kwds.items()))
- use_count[key] += 1 # count a use of this key
try:
- result = cache[key]
- wrapper.hits += 1
+ with lock:
+ use_count[key] += 1 # count a use of this key
+ result = cache[key]
+ wrapper.hits += 1
except KeyError:
result = user_function(*args, **kwds)
- cache[key] = result
- wrapper.misses += 1
- if len(cache) > maxsize:
- # purge the 10% least frequently used entries
- for key, _ in nsmallest(maxsize // 10,
- use_count.items(),
- key=itemgetter(1)):
- del cache[key], use_count[key]
+ with lock:
+ use_count[key] += 1 # count a use of this key
+ cache[key] = result
+ wrapper.misses += 1
+ if len(cache) > maxsize:
+ # purge the 10% least frequently used entries
+ for key, _ in nsmallest(maxsize // 10,
+ use_count.items(),
+ key=itemgetter(1)):
+ del cache[key], use_count[key]
return result
def clear():
"""Clear the cache and cache statistics"""
- cache.clear()
- use_count.clear()
- wrapper.hits = wrapper.misses = 0
+ with lock:
+ cache.clear()
+ use_count.clear()
+ wrapper.hits = wrapper.misses = 0
wrapper.hits = wrapper.misses = 0
wrapper.clear = clear
@@ -161,9 +170,10 @@ def lru_cache(maxsize=100):
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
kwd_mark = object() # separate positional and keyword args
+ lock = Lock()
@wraps(user_function)
def wrapper(*args, **kwds):
@@ -171,20 +181,25 @@ def lru_cache(maxsize=100):
if kwds:
key += (kwd_mark,) + tuple(sorted(kwds.items()))
try:
- result = cache.pop(key)
- wrapper.hits += 1
+ with lock:
+ result = cache[key]
+ del cache[key]
+ cache[key] = result # record recent use of this key
+ wrapper.hits += 1
except KeyError:
result = user_function(*args, **kwds)
- wrapper.misses += 1
- if len(cache) >= maxsize:
- cache.popitem(0) # purge least recently used cache entry
- cache[key] = result # record recent use of this key
+ with lock:
+ cache[key] = result # record recent use of this key
+ wrapper.misses += 1
+ if len(cache) > maxsize:
+ cache.popitem(0) # purge least recently used cache entry
return result
def clear():
"""Clear the cache and cache statistics"""
- cache.clear()
- wrapper.hits = wrapper.misses = 0
+ with lock:
+ cache.clear()
+ wrapper.hits = wrapper.misses = 0
wrapper.hits = wrapper.misses = 0
wrapper.clear = clear