我正在尝试在Python
中实现并发字典 - 更具体地说,字典将由两个线程使用,一个将使用其clear
和update
方法的线程,以及另一个将直接访问其值(即,通过其__getitem__
方法)。实施如下:
from threading import Lock, current_thread
class ThreadSafeDict(dict):
def __init__(self, *args, **kwargs):
self._lock = Lock()
super(ThreadSafeDict, self).__init__(*args, **kwargs)
def clear(self, *args, **kwargs):
print("thread {} acquiring clear lock".format(current_thread().ident))
self._lock.acquire()
print("thread {} acquired clear lock".format(current_thread().ident))
super(ThreadSafeDict, self).clear(*args, **kwargs)
print("thread {} releasing clear lock".format(current_thread().ident))
self._lock.release()
print("thread {} released clear lock".format(current_thread().ident))
def __getitem__(self, *args, **kwargs):
print("thread {} acquiring getitem lock".format(current_thread().ident))
self._lock.acquire()
print("thread {} acquired getitem lock".format(current_thread().ident))
val = super(ThreadSafeDict, self).__getitem__(*args, **kwargs)
print("thread {} releasing getitem lock".format(current_thread().ident))
self._lock.release()
print("thread {} released getitem lock".format(current_thread().ident))
return val
def update(self, *args, **kwargs):
print("thread {} acquiring update lock".format(current_thread().ident))
self._lock.acquire()
print("thread {} acquiring update lock".format(current_thread().ident))
super(ThreadSafeDict, self).update(*args, **kwargs)
print("thread {} releasing update lock".format(current_thread().ident))
self._lock.release()
print("thread {} released update lock".format(current_thread().ident))
我正在使用此脚本测试实现:
import threading
import random
import time
from threadsafedict import ThreadSafeDict
def reader(tsd):
while True:
try:
val = tsd[1]
except KeyError:
pass
interval = random.random() / 2
time.sleep(interval)
def writer(tsd):
while True:
tsd.clear()
interval = random.random() / 2
time.sleep(interval)
tsd.update({1: 'success'})
def main():
tsd = ThreadSafeDict()
w_worker = threading.Thread(target=writer, args=(tsd,))
r_worker = threading.Thread(target=reader, args=(tsd,))
w_worker.start()
r_worker.start()
w_worker.join()
r_worker.join()
if __name__ == '__main__':
main()
示例输出:
thread 140536098629376 acquiring clear lock
thread 140536098629376 acquired clear lock
thread 140536098629376 releasing clear lock
thread 140536098629376 released clear lock
thread 140536090236672 acquiring getitem lock
thread 140536090236672 acquired getitem lock
thread 140536090236672 acquiring getitem lock
thread 140536098629376 acquiring update lock
我做错了什么?
(我意识到这种并发性在CPython
中已经是安全的,但我试图与实现无关)
答案 0 :(得分:1)
问题是当super().__getitem__()
方法中的ThreadSafeDict.__getitem()__
调用无法找到具有给定密钥的项目时,会引发KeyError
,导致__getitem__()
的剩余部分要跳过的方法。这意味着锁定将不会被释放,并且任何以后对任何方法的调用都将被永久阻止,等待获取永远不会被解锁的锁定。
你可以看到,这是因为没有发布'和'发布'获得的getitem锁定后的消息'消息,在该摘录中紧跟另一个尝试获取读取线程的锁定。在您的测试代码中,如果读取线程在执行clear()之后但在写入线程执行update()之前的时间间隔内运行,则读取线程将始终处于此状态。
要修复,请在__getitem__()
方法中捕获KeyError异常,然后释放锁定,然后重新引发异常。 ' try
/ finally
'构造提供了一种非常直接的方法来做到这一点;事实上,这是使用' finally
'。
或者你可以在获得锁定之前和之前调用super().__getitem__()
之前检查所需的密钥是否存在,尽管如果通常期望密钥存在,这会对性能造成影响。
ThreadSafeDict
继承dict
并不是一个好主意。这会导致ThreadSafeDict
继承所有dict
方法(例如,__setitem__()
),如果有人使用了这些方法,那么您未被覆盖的任何方法都会绕过您的锁。如果您还没有准备好覆盖所有这些方法,那么让基础dict
成为您班级的实例成员会更安全。