Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/292.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
不使用WeakValueDictionary的Python行锁定_Python_Multithreading - Fatal编程技术网

不使用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倍。但是既然你说了。。。无论什么关于内存泄漏,我不认为这是一个问题,因为所有锁都会被
缓存
本身所破坏。