python - list(dict.items()) 是线程安全的吗?
问题描述
下面例子中的使用list(d.items())
安全吗?
import threading
n = 2000
d = {}
def dict_to_list():
while True:
list(d.items()) # is this safe to do?
def modify():
for i in range(n):
d[i] = i
if __name__ == "__main__":
t1 = threading.Thread(target=dict_to_list, daemon=True)
t1.start()
t2 = threading.Thread(target=modify, daemon=True)
t2.start()
t2.join()
这个问题背后的背景是,字典项视图上的迭代器在每一步检查字典大小是否发生变化,如下例所示。
d = {}
view = d.items() # this is an iterable
it = iter(view) # this is an iterator
d[1] = 1
print(list(view)) # this is ok, it prints [(1, 1)]
print(list(it)) # this raises a RuntimeError because the size of the dictionary changed
因此,如果上面第一个示例中的调用list(...)
可以被中断(即线程t1
可以释放 GIL),那么第一个示例可能会导致 RuntimeErrors 在线程中发生t1
。有消息称该操作不是原子操作,请参见此处。但是,我无法让第一个示例崩溃。
我知道在这里做的安全的事情是使用一些锁而不是试图依赖某些操作的原子性。但是,我正在使用类似代码的第三方库中调试一个问题,并且我不一定要直接更改。
解决方案
简短的回答:可能没问题,但无论如何都要使用锁。
使用dis
你可以看到这list(d.items())
实际上是两个字节码指令(6
和8
):
>>> import dis
>>> dis.dis("list(d.items())")
1 0 LOAD_NAME 0 (list)
2 LOAD_NAME 1 (d)
4 LOAD_METHOD 2 (items)
6 CALL_METHOD 0
8 CALL_FUNCTION 1
10 RETURN_VALUE
在 Python FAQ 中,它说(通常)用 C 实现的东西是原子的(从正在运行的 Python 程序的角度来看):
一般来说,Python 只提供在字节码指令之间切换线程;[...]。因此,从 Python 程序的角度来看,每条字节码指令以及因此从每条指令到达的所有 C 实现代码都是原子的。
[...]
例如,以下操作都是原子的 [...]
D.keys()
list()
在 C 中实现并在 C中d.items()
实现,因此每个都应该是原子的,除非它们最终以某种方式调用 Python 代码(如果它们调用您使用 Python 实现覆盖的 dunder 方法,则可能发生这种情况)或者如果您是使用一个子类dict
而不是一个真实的dict
,或者如果他们的 C 实现释放了 GIL。依赖它们是原子的并不是一个好主意。
您提到iter()
如果其基础可迭代更改大小,则会出错,但这与此处无关,因为.keys()
,.values()
并.items()
返回一个视图对象,并且基础对象更改没有问题:
d = {"a": 1, "b": 2}
view = d.items()
print(list(view)) # [("a", 1), ("b", 2)]
d["c"] = 3 # this could happen in a different thread
print(list(view)) # [("a", 1), ("b", 2), ("c", 3)]
如果您一次在多个指令中修改 dict,有时您会d
处于不一致的状态,其中一些修改已经完成,有些还没有,但您不应该RuntimeError
像您一样with iter()
, 除非你以非原子的方式修改它。
推荐阅读
- java - 存储访问框架 - 无法从 uri 获取文档树(从 Drive 应用程序返回)
- clojure - 如何使用clojure中的索引替换字符串中的字符
- javascript - 逐帧向前和向后
- laravel - 关系前导零的Laravel问题
- javascript - 我有 Node Js API,我需要在某些端点上授予顺序执行
- linux - 将输出同时重定向到控制台和文件的命令在 bash 中运行良好。但是我如何让它在 korn shell(ksh) 中工作
- python - 我从一个简单的副本中得到“KeyError:1” - Python机器学习
- python - 基于另一个列表索引的订单列表
- vb.net - 在页面加载时修复文本框中的日期格式
- php - 从选择查询中的两个不同表中减去两个不同的列总和