在Python中使用线程的全局字典
访问或修改字典中的值是线程安全的吗?
我有一个全局的字典 foo
,还有多个线程,分别有不同的ID,比如 id1
、id2
,一直到 idn
。如果我知道每个线程只会处理与自己ID相关的值,比如线程 id1
只会处理 foo[id1]
,那么我在访问和修改 foo
的值时,是否可以不加锁呢?
5 个回答
因为我需要类似的东西,所以我来到了这里。我把你们的回答总结成了这个简短的代码片段:
#!/usr/bin/env python3
import threading
class ThreadSafeDict(dict) :
def __init__(self, * p_arg, ** n_arg) :
dict.__init__(self, * p_arg, ** n_arg)
self._lock = threading.Lock()
def __enter__(self) :
self._lock.acquire()
return self
def __exit__(self, type, value, traceback) :
self._lock.release()
if __name__ == '__main__' :
u = ThreadSafeDict()
with u as m :
m[1] = 'foo'
print(u)
这样,你可以使用 with
这个结构来在操作你的 dict()
时保持锁定状态。
让每个线程使用独立数据的最佳、安全、便携的方法是:
import threading
tloc = threading.local()
现在,每个线程都在使用一个完全独立的 tloc
对象,尽管它是一个全局名称。线程可以在 tloc
上获取和设置属性,如果需要字典,可以使用 tloc.__dict__
等等。
线程的本地存储在线程结束时会消失;为了让线程记录它们的最终结果,可以在它们结束之前,把结果 put
到一个公共的 Queue.Queue
实例中(这个是本质上线程安全的)。同样,线程要处理的数据的初始值可以在启动线程时作为参数传递,或者从一个 Queue
中获取。
其他不成熟的方法,比如希望看起来是原子操作的操作确实是原子操作,可能在某个特定版本的Python中对特定情况有效,但在升级或移植时很容易就会出问题。既然有一种合适、干净、安全的架构这么容易安排,而且便携、方便、快速,就没有必要冒这样的风险。
假设我们在使用CPython:可以说是,也可以说不是。实际上,从一个共享字典中获取或存储值是安全的,因为多个线程同时进行读写请求不会导致字典损坏。这是因为实现中有一个叫做全局解释器锁(“GIL”)的机制。也就是说:
线程A在运行:
a = global_dict["foo"]
线程B在运行:
global_dict["bar"] = "hello"
线程C在运行:
global_dict["baz"] = "world"
即使这三个线程同时尝试访问字典,也不会导致字典损坏。解释器会以某种未定义的方式将它们串行处理。
但是,以下操作的结果是未定义的:
线程A:
if "foo" not in global_dict:
global_dict["foo"] = 1
线程B:
global_dict["foo"] = 2
因为线程A中的测试/设置操作不是原子的(也就是存在“检查时机/使用时机”的竞争条件)。所以,通常最好是,如果你加锁:
from threading import RLock
lock = RLock()
def thread_A():
with lock:
if "foo" not in global_dict:
global_dict["foo"] = 1
def thread_B():
with lock:
global_dict["foo"] = 2