我根本没有在Python中使用线程,并且认为这个问题是一个完全陌生的人。
我想知道defaultdict
是否是线程安全的。让我解释一下:
我有
d = defaultdict(list)
默认情况下会为缺少的密钥创建一个列表。假设我有多个线程同时开始执行此操作:
d['key'].append('value')
最后,我应该以{{1}}结束。但是,如果['value', 'value']
不是线程安全的,那么在检查defaultdict
之后和{{1}之前主题1 会产生主题2 },它将导致交错,另一个线程将在if 'key' in dict
中创建列表并附加d['key'] = default_factory()
。
然后当主题1 再次执行时,它将从d['key']
继续,这会破坏现有的列表和值,我们将以'value'
结束。
我看了CPython source code for defaultdict。但是,我找不到任何锁或互斥锁。我想只要记录在案,它就不是线程安全的。
昨晚有人在IRC上表示Python上有GIL,所以它在概念上是线程安全的。有人说线程不应该在Python中完成。我很困惑。想法?
答案 0 :(得分:23)
这是线程安全的,在这种特定情况下。
要知道在Python切换线程时理解为什么很重要。 CPython只允许在Python字节码步骤之间切换线程。这就是GIL的用武之地;每N字节代码指令释放一次锁,并且可以进行线程切换。
d['key']
代码由一个字节码(BINARY_SUBSCR
)处理,触发在字典上调用.__getitem__()
方法。
defaultdict
,配置为list
作为默认值工厂,并使用字符串值作为键,在C中完全处理dict.__getitem__()
方法 ,< em>和 GIL永远不会被解锁,使dict[key]
查找线程安全。
注意那里的资格;如果您使用不同的默认值工厂创建defaultdict
实例,使用Python代码(例如lambda: [1, 2, 3]
),所有投注都会关闭,因为这意味着C代码回调到Python代码,并且在执行lambda
函数的字节码时可以再次释放GIL。这同样适用于键,当使用在Python代码中实现__hash__
或__eq__
的对象时,可以在那里进行线程切换。接下来,如果工厂是用明确释放GIL的C代码编写的,则可以进行线程切换,并且线程安全不在窗口。