Python的dict.pop方法是原子性的吗?

14
似乎可以合理地认为,dict.pop 操作是原子性的,因为如果指定的键不存在且没有提供默认值,则会引发 KeyError,例如:
d.pop(k)

然而,文件似乎没有具体解释这一点,至少在专门记录dict.pop的部分中没有。

当我回顾我的答案时,这个问题浮现在我脑海中,我在那个答案中使用了这种模式。

if k in d: del d[k]

当时我没有考虑到一个关键字可能在if语句中存在,但在执行del时不存在。如果dict.pop确实提供了一个原子操作的替代方案,那么我应该在我的回答中注明。


2
相关:哪些全局值的变异是线程安全的? - Ashwini Chaudhary
2个回答

32
对于默认类型,dict.pop() 是一个 C 函数调用,这意味着它只执行了 一次 字节码评估。这使得该调用是原子的。
Python 线程只在字节码边界时才切换,因为它们受字节码评估循环控制。一些 Python C 函数确实会回调到 Python 代码中(考虑 __dunder__ 特殊方法钩子),但对于默认的 dict 类型,dict.pop() 方法并不会进行回调。

超出了我的解释能力范围。只有一个问题:有人知道Jython的情况吗? - mike rodent
请参阅Jython并发文档Jython通过使用Java的ConcurrentHashMap实现了dict和set。这意味着您可以仅使用这些标准Python类型,仍然获得高性能并发性。(它们也像CPython中一样是原子性的,我们将在描述中说明。) - Martijn Pieters
在同一章节中,列出了原子操作的更多内容:原地修改字典(例如添加一个项目或调用clear方法) - Martijn Pieters

5

实际上,dict.pop()不是原子操作。例如,如果您将对象用作字典的键,则Python必须调用对象的__hash__()实现。但是,您可以使用dict.popitem(),它是真正的原子操作。


2
你的回答与最佳和被接受的答案根本相矛盾。你仍然认为你的回答是正确的吗?如果是,为什么?否则,你能否加上免责声明或其他说明,以便像我这样的人不会感到困惑? - Yatharth Agarwal
并非根本如此。@martijn-pieters提到了标准类型(实际上是基本类型)。但通常指望dict.pop()始终是原子操作并不是一个好主意。 - renskiy
当你说“如果你将对象用作字典的键”,你所指的对象是什么?我以为在Python中,所有东西都是对象? - Yatharth Agarwal
2
我指的是将用户定义类的实例用作字典键。 - renskiy
2
当执行像 d.pop(key, None) 这样的操作时,只有一个线程会得到值,其他线程会得到 None,对吗?即使两个线程都执行了 hash(key),只有一个线程会赢得从字典中删除它的竞争。例如 d.pop(generate_key(), None) 可能不是原子性的,因为 generate_key() 不是原子性的,但从字典中删除仍然是原子性的。 - lumbric
2
@YatharthAgarwal 可接受答案的标准已经改变。在2019年,我认为我们会说martijn-pieters没有回答问题,因为问题没有提到默认类型。这也取决于实现。永远不要被声望所吓倒。 - andy256

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