Python字典是线程安全的吗?

26

有些人说Python字典是线程安全的。这是否意味着我可以在迭代过程中修改字典中的项目?

4个回答

65
其他回答已经正确回答了你实际关心的问题:在迭代字典时是否可以修改其中的项?线程安全与此问题无关,无论如何,不能在迭代过程中修改字典。然而,你的问题标题是关于线程安全的,并且开头提到:“有人说Python字典是线程安全的”。我不知道“有些人”是谁,但如果他们确实这样说过(而不是你误解了他们的陈述;-),除非有严格的限制条件,否则他们是错的。
某些操作,例如不改变字典键集的操作,在当前CPython实现中恰好是线程安全的——但是你不应该指望这种情况,除非你严格控制你的代码将运行的Python版本,因为这种线程安全性并不是由Python的语言规范保证的,因此包括CPython未来版本在内的其他实现可能不会提供它。如果每个线程只是“读取”字典(对其进行索引、循环等操作),而没有线程对其进行任何赋值或删除操作,则在当前CPython实现中,这种情况是安全的;实际上,如果某个线程为已经存在的键分配了一个新值,那么这也是线程安全的(其他线程可能会看到该键的先前值或下一个值,这取决于线程的时间安排方式,但在当前CPython实现中,不会发生崩溃、死锁或出现突然出现的疯狂值)。然而,像"d [k] + = 1"这样的操作(假设k以前存在,并且它的值是一个数字)从严格意义上讲并不是线程安全的(任何情况下都是如此!),因为它可以被视为"d[k]=d[k]+1"——在竞争条件下,两个线程可能都读取d[k]的旧值,然后将其增加1,并将相同的新值存储在该槽中……因此,总体效果只增加1,而不是通常情况下的2。
回到你的另一个问题……“仅读取”字典,以及向已经存在于字典中的键分配新值,也是你可以在迭代字典的循环体中执行的操作,但你不能修改字典中的键集(不能添加任何键,也不能删除任何键),但在这种情况下允许的操作包括在线程情况下可能有问题的+=。例如:
>>> d = dict.fromkeys(range(5), 0)
>>> for k in d: d[k] += 1
... 
>>> d
{0: 1, 1: 1, 2: 1, 3: 1, 4: 1}

this 的行为是由 Python 的标准语义保证的,因此语言的不同实现都应该保留它。


1
你先前说:“如果每个线程仅“读取”字典(对其进行索引、循环等),并且没有任何线程对其执行赋值或删除操作,则在当前的CPython实现中,这种情况是安全的”,最后你说:“特定的操作——为现有键设置新值是被允许的......这种行为由Python的标准化语义保证,因此不同的语言实现都应该保留它”。我感到很难区分这两种情况。请您能否再详细解释一下?提前致谢! - RayLuo

32
这两个概念完全不同。线程安全是指两个线程不能同时修改同一个对象,从而使系统处于不一致状态。
也就是说,在迭代字典时不能修改它。请参见文档

在迭代过程中,不应该更改字典p。自Python 2.1以来,可以安全地修改键的值,但前提是键集不会更改。


8
我认为它们是相关的。如果一个线程在迭代,而另一个线程修改字典会怎样? - Prof. Falken

14

不会。如果您尝试在迭代之间更改字典的大小,则现代Python版本将引发异常。

>>> d={'one':1, 'two':2}
>>> for x in d:
...    d['three']=3
...    print x
...
two
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: dictionary changed size during iteration

请注意,您不需要使用线程即可看到这一点。


1

如果您在另一个线程中同时添加或删除元素,则无法迭代字典。您可能会收到“RuntimeError:dictionary changed size during iteration”或“KeyError”错误。

请参见实时示例,您可以对其进行操作。

是的,在不同的线程中,您可以同时迭代、更改元素的值、获取元素而不会出现异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接