collections.defaultdict是线程安全的吗?

25

我从未在Python中使用过线程,作为一个完全陌生的人提出这个问题。

我想知道 defaultdict 是否是线程安全的。让我解释一下:

我有

d = defaultdict(list)

默认情况下,它会为缺失的键创建一个列表。假设我启动了多个线程同时执行此操作:

d['key'].append('value')

最后,我应该得到['value', 'value']。但是,如果defaultdict不是线程安全的,在检查 if 'key' in dict 之后,并在d ['key'] = default_factory()之前,如果线程1让步给线程2,那么就会发生交错,并且另一个线程可能会在d ['key']中创建列表并追加'value'

然后当线程1再次执行时,它将从d ['key'] = default_factory()继续执行,这将销毁现有的列表和值,我们将以['key']结束。

我查看了CPython defaultdict源代码。然而,我找不到任何锁或互斥体。我猜只要它被记录下来,那么它就不是线程安全的。

昨晚一些人在IRC上说Python有GIL,因此从概念上讲是线程安全的。有人说Python不应该使用多线程。我很困惑。你有什么想法?


可能会有所帮助:https://groups.google.com/forum/#!topic/comp.lang.python/9ZnBQrYun1w - user2579943
1个回答

33

在这个特定的情况下,它是线程安全的。

要知道何时Python切换线程很重要。CPython只允许在Python字节码步骤之间在线程之间切换。这就是GIL的作用,每N个字节码指令锁都会被释放,可以进行线程切换。

d['key']代码由一个字节码(BINARY_SUBSCR)处理,它触发调用字典的.__getitem__()方法。

使用list作为默认值工厂配置的defaultdict,并使用字符串作为键,完全在C中处理dict.__getitem__()方法,GIL从未解锁,使得dict[key]查询是线程安全的。

请注意限定词;如果您使用不同的默认值工厂创建defaultdict实例,例如使用Python代码(lambda: [1, 2, 3]),那么所有赌注都将失效,因为这意味着C代码会回调到Python代码,而在执行lambda函数的字节码时GIL可能会再次被释放。使用实现了__hash____eq__的对象作为键时也是如此。接下来,如果工厂是用C代码编写的,它明确释放GIL,则可以进行线程切换,并且线程安全性将不复存在。


1
由于这似乎没有在文档中提到,这听起来只是CPython实现的细节 - 虽然仍然有用要知道。 - martineau
5
+1 提到Python编写的工厂可以触发GIL的释放。不幸的是,情况变得更加复杂:如果对象被释放并且具有__del__,那么任何Py_DECREF都有可能触发GIL。这样即使是纯C代码也可能无意中导致GIL的释放——尽管这种情况比较少见,但确实会发生。 - user4815162342
4
开发人员需要注意在 C/Python 代码执行来回切换时,全局解释器锁的释放。这很有趣。谢谢。 - ahmet alp balkan
这个 defaultdict(lambda: defaultdict(lambda: False)) 的用法是否线程安全,因为没有字典值是列表。 - Krishna Oza
1
@darth_coder:这取决于键,因为如果键实现了Python的__hash____eq__方法,hash(key)可以调用Python。 - Martijn Pieters

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