Python dict.get()锁
当我使用Python dict.get()锁,python,multithreading,dictionary,multiprocessing,Python,Multithreading,Dictionary,Multiprocessing,当我使用dictionary.get()函数时,它是否锁定了整个字典?我正在开发一个多进程和多线程程序。字典用作状态表来跟踪数据。我必须对字典施加大小限制,因此每当达到限制时,我必须根据时间戳对表进行垃圾收集。当垃圾回收在整个表中迭代时,当前实现将延迟添加操作 我将有2个或更多的线程,一个只是添加数据,一个只是做垃圾收集。在我的程序中,性能是处理流数据的关键。我的程序正在接收流数据,每当它接收到消息时,它必须在状态表中查找它,然后添加记录(如果它不存在),或者复制某些信息,然后沿管道发送 我曾想
dictionary.get()
函数时,它是否锁定了整个字典?我正在开发一个多进程和多线程程序。字典用作状态表来跟踪数据。我必须对字典施加大小限制,因此每当达到限制时,我必须根据时间戳对表进行垃圾收集。当垃圾回收在整个表中迭代时,当前实现将延迟添加操作
我将有2个或更多的线程,一个只是添加数据,一个只是做垃圾收集。在我的程序中,性能是处理流数据的关键。我的程序正在接收流数据,每当它接收到消息时,它必须在状态表中查找它,然后添加记录(如果它不存在),或者复制某些信息,然后沿管道发送
我曾想过使用多处理
来同时执行搜索和添加操作,但如果我使用进程,我必须为每个进程创建状态表的副本,在这种情况下,同步的性能开销太高。我还了解到,multiprocessing.manager.dict()
也在锁定每个CRUD操作的访问。我无法为它节省开销,因此我当前的方法是使用线程
所以我的问题是,当一个线程在表上执行.get()
,del dict['key']
操作时,是否会阻止另一个插入线程访问它
注意:我已经阅读了大多数SO的python字典相关文章,但似乎找不到答案。大多数人只回答说,即使python字典操作是原子操作,使用锁进行插入/更新也更安全。我正在处理大量的流式数据,因此每次锁定对我来说并不理想。请告知是否有更好的方法。锁用于避免争用条件,因此没有两个线程可以同时更改dict,因此建议您使用锁,否则可能会进入争用条件,导致程序失败。互斥锁可用于处理两个线程。如果对字典中的键进行散列或比较的过程可以调用任意Python代码(基本上,如果键不是用C实现的所有Python内置类型,例如
str
,int
,float
,等等),则是,可能会出现竞态条件,即在解决铲斗碰撞时(在相等测试期间),释放GIL,另一个线程可能会跳入,并导致所比较的对象从dict
中消失。他们试图确保它实际上不会使解释器崩溃,但过去它一直是错误的来源
如果这是一种可能性(或者您使用的是非CPython解释器,没有GIL提供这样的基本保证),那么您应该真正使用锁来协调访问。在CPython上,只要您使用现代Python 3,成本就会相当低;锁上的争用应该相当低,因为GIL确保一次只运行一个线程;大多数情况下,您的锁应该是无争用的(因为争用在GIL上),因此使用它的增量成本应该相当小
注释:您可以考虑使用<代码>集合.OrrordEddit < /Cord>以简化限制表大小的过程。使用
OrderedDict
,您可以通过如下方式向表中添加内容,将大小限制作为严格的LRU(最近使用最少的)系统来实施:
with lock:
try:
try:
odict.move_to_end(key) # If key already existed, make sure it's "renewed"
finally:
odict[key] = value # set new value whether or not key already existed
except KeyError:
# move_to_end raising key error means newly added key, so we might
# have grown larger than limit
if len(odict) > maxsize:
odict.popitem(False) # Pops oldest item
with lock:
# move_to_end optional; if using key means it should live longer, then do it
# if only setting key should refresh it, omit move_to_end
odict.move_to_end(key)
return odict[key]
使用方法如下:
with lock:
try:
try:
odict.move_to_end(key) # If key already existed, make sure it's "renewed"
finally:
odict[key] = value # set new value whether or not key already existed
except KeyError:
# move_to_end raising key error means newly added key, so we might
# have grown larger than limit
if len(odict) > maxsize:
odict.popitem(False) # Pops oldest item
with lock:
# move_to_end optional; if using key means it should live longer, then do it
# if only setting key should refresh it, omit move_to_end
odict.move_to_end(key)
return odict[key]
这确实需要一个锁,但它也减少了垃圾收集的工作量,因为垃圾收集太大了,从“检查每一把钥匙”(
O(n)
work)到“在不看任何其他东西的情况下弹出最旧的项目”(O(1)
work)。这并没有解决您的主要问题,所以我将它作为一个注释。每当你担心比赛条件时,你都想使用一步操作。两步get
然后del
是不可取的-改用一步pop
。我在这里避免使用“原子”一词,因为我认为在你的情况下,即使是一步方法也不可能是原子的,不需要额外的努力。谢谢你的评论。在我的例子中,我设计了statutable,使得所有键都是字典值内值的散列和。它是在运行时生成的,我不跟踪它们。pop需要使用pop
项的键。我想我现在不能用这个。但是将2操作更改为1是一个好主意。如何在没有键的情况下使用get
?哦,我的意思是,使用.get()
时,如果键不存在,它将返回None。但是.pop()
将引发keyrerror
。无论如何,我必须为dict.keys()中的键执行,所以它仍然必须执行两次。我的键都是字符串类型。那么这是否意味着我不必关心比赛条件?您弹出最旧项目的方法是一个非常好的主意。它可以为程序节省大量时间。@ThuYeinTun:如果是所有str
键,并且您使用的是CPython引用解释器,那么大多数影响单个项的单个函数调用和逻辑上最为原子化的操作都将以原子方式进行,但这仍然会限制您;如果您需要做两件事(即使是看起来像一件事的事情,比如mydict[key]+=1
实际上是多个步骤,可能会在线程之间发生冲突并最终删除增量),那么dict
的所有使用都需要锁定,以确保多级操作以原子方式进行。