【问题标题】:Is collections.defaultdict thread-safe?collections.defaultdict 是线程安全的吗?
【发布时间】:2013-07-16 16:49:53
【问题描述】:

我根本没有在 Python 中使用过线程,并且作为一个完全陌生的人问这个问题。

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

我有

d = defaultdict(list)

默认情况下会为丢失的键创建一个列表。假设我有多个线程同时开始执行此操作:

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

最后,我应该以['value', 'value'] 结束。但是,如果defaultdict不是线程安全的,如果线程1在检查if 'key' in dict之后和d['key'] = default_factory()之前让给线程2,就会导致交错,而另一个线程将在d['key'] 中创建列表并可能附加'value'

那么当线程1再次执行时,它会从d['key'] = default_factory()继续,这将破坏现有的列表和值,我们最终会进入['key']

我看了CPython source code for defaultdict。但是,我找不到任何锁或互斥锁。我猜它不是线程安全的,只要它被记录在案。

昨晚在 IRC 上有些人说 Python 上有 GIL,所以它在概念上是线程安全的。有人说线程不应该在 Python 中完成。我很困惑。想法?

【问题讨论】:

标签: python defaultdict python-collections


【解决方案1】:

这是线程安全的,在这种特定情况下

了解为什么理解 Python 何时切换线程很重要。 CPython 只允许在 Python 字节码步骤之间切换线程。这就是 GIL 的用武之地;每 N 个字节的代码指令释放锁并进行线程切换。

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

一个defaultdict,配置list作为默认值工厂,使用字符串值作为键,处理C语言中的dict.__getitem__()方法完全 GIL 永远不会解锁,使dict[key] 查找线程安全。

注意那里的限定条件;如果您创建一个带有 不同 默认值工厂的 defaultdict 实例,该工厂使用 Python 代码(例如 lambda: [1, 2, 3]),那么所有的赌注都将被取消,因为这意味着 C 代码回调到 Python代码和 GIL 可以在执行 lambda 函数的字节码时再次释放。这同样适用于键,当使用在 Python 代码中实现 __hash____eq__ 的对象时,可以在那里进行线程切换。接下来,如果工厂是用显式释放 GIL 的 C 代码编写的,则可能会发生线程切换,并且线程安全性已被忽略。

【讨论】:

  • 因为它[显然]不在文档中,这听起来只是一个 CPython 实现细节——不过,了解它仍然很有用。
  • +1 提到 Python 编写的工厂可以触发 GIL 的发布。不幸的是,它变得更加棘手:如果对象被释放并具有__del__,GIL 可能会在任何Py_DECREF 上触发。这样一来,即使是纯 C 代码也可能在不知不觉中导致 GIL 的发布——这固然是病态的,但它可能会发生。
  • 非常有趣的是,开发人员应该注意 C/Python 代码执行之间的 GIL 版本。谢谢。
  • 这种defaultdict(lambda: defaultdict(lambda: False)) 的用法是否是线程安全的,因为没有任何字典值是列表的。
  • @darth_coder:这取决于键,因为如果键实现 Python __hash____eq__ 方法,hash(key) 可以调用 Python。
猜你喜欢
  • 2021-10-12
  • 2015-04-18
  • 2011-10-07
  • 2012-03-02
  • 2011-10-28
  • 2023-03-14
  • 2016-08-24
  • 2011-08-16
  • 2011-09-28
相关资源
最近更新 更多