不使用WeakValueDictionary的Python行锁定
我想防止在每个元素的基础上并发访问字典。具体来说,我有一个不使用WeakValueDictionary的Python行锁定,python,multithreading,Python,Multithreading,我想防止在每个元素的基础上并发访问字典。具体来说,我有一个缓存类: class Cache: def __init__(self): self._values = {} def query(self, item): try: return self._values[item] except KeyError: value = compute_value(item) # Expensiv
缓存
类:
class Cache:
def __init__(self):
self._values = {}
def query(self, item):
try:
return self._values[item]
except KeyError:
value = compute_value(item) # Expensive operation
self._values[item] = value
return value
换句话说,Cache
应该根据需要计算项目值,然后缓存它们以供以后查询
缓存
用于多个线程。我希望避免线程同时调用同一项的compute\u value(…)
:如果线程A
和B
都请求my\u项的值,那么只有A
应该计算它B
应等待其结果,然后使用缓存的值
我已经按照如下方式实现了这一点:
from threading import Lock
from weakref import WeakValueDictionary
class Cache:
def __init__(self):
self._values = {}
self._locks = WeakValueDictionary()
def query(self, item):
with self._locks.setdefault(item, Lock()):
try:
return self._values[item]
except KeyError:
value = compute_value(item)
self._values[item] = value
return value
这很有效。特别是,我使用的WeakValueDictionary
确保了对同一项
的并发查询得到相同的锁
,但锁不会永远留在内存中
问题是,我的应用程序实际上动态创建了许多缓存的实例。因此,调用self.\u values=WeakValueDictionary()
成为性能瓶颈
我正在寻找一种解决方案,它可以让我实现同样的任务,但是使用普通的Python字典,而不是WeakValueDictionary
。我试过:
class Cache:
def __init__(self):
self._values = {}
self._locks = {} # No more WeakValueDictionary
def query(self, item):
with self._locks.setdefault(item, Lock()):
# as before...
del self._locks[item]
但这并没有完全阻止并发访问。具体地说,如果线程A
和B
获得了相同的锁,并且A
在B
仍然持有它的情况下删除了它,那么另一个线程C
可以出现并获得同一行的新锁,从而与B
同时访问它
有人知道如何实现这一点吗?可能是使用其他原语,如信号量?我真的无法想象自动同步的方式,所以我会使用主锁来保护锁访问和挂起的请求计数。只有在没有其他请求挂起(计数==0)时,才应删除项锁。代码较大,但应该是防弹的:
class Cache:
def __init__(self):
self._values = {}
self._locks = {}
self._master_lock = Lock()
def query(self, item):
with self._master_lock:
if item in self._values: # if value is ready return it immediately
return self._values[item]
lock = self._locks.setdefault( # else build or use an item lock
item, [Lock(), 0]) # and say we are pending on it
lock[1] += 1
with lock[0]: # release master lock and acquire item one
exc = None # be prepared to any exception
try: # read or compute (first time only) the value
val = self._values.setdefault(
item, compute_value(item))
except Exception as e:
exc = e # note the exception for later re-raise
with self._master_lock: # release item lock and take again master one
lock[1] -= 1 # we are no longer pending
if lock[1] == 0: # if no other thread is either
del self._locks[item] # delete the item lock
if exc:
raise exc # eventually re-raise
return val
如何确认性能瓶颈是self.\u values=WeakValueDictionary()
?这没有道理。我做了一个简介:
%timeit locks = WeakValueDictionary()
2.52 µs ± 50.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
如您所见,每个循环的成本仅为2.52µs
。您需要创建数百万个缓存
对象,只需两秒钟
在我看来,性能瓶颈应该来自self.\u locks.setdefault(item,Lock()):
,因为它将为每个并发查询创建新的Lock
对象
实际上有一件事我不能完全理解,为什么在查询后需要删除Lock
对象?难道不是每个钥匙
都有相应的锁
吗?当你删除这个键
时,你也可以删除它的锁
我使用cProfile发现在我的机器上实例化30000个WeakValue Dictionary
s大约需要100毫秒。这与你的尺寸相符。这对于我的用例来说太多了。创建Lock
s更便宜,而且不是主要成本(同样,在我的环境中)。我正在删除Lock
对象以防止内存泄漏。创建Lock
更便宜,但也更频繁。在我的环境中,创建WeakValueDictionary
的成本是创建Lock
的20倍。但是既然你说了。。。无论什么关于内存泄漏,我不认为这是一个问题,因为所有锁都会被缓存本身所破坏。