在 python 官方文档中有这样一段关于 GIL 的解释:
The mechanism used by the CPython interpreter to assure that only one thread executes Python bytecode at a time. This simplifies the CPython implementation by making the object model (including critical built-in types such as dict) implicitly safe against concurrent access. Locking the entire interpreter makes it easier for the interpreter to be multi-threaded, at the expense of much of the parallelism afforded by multi-processor machines.
简单的说就是在实现的时候 CPython 解释器假定只有一个线程执行 python 的字节码,保证单一操作的原子性,使类似于 dict 这个数据类型隐式的线程安全。
但是我们对字典的操作通常是复合操作(插入、删除、更新),这些步骤并不是原子性的。在多线程抢占解释器锁时(特别是多核CPU的机器中),就可能会导致数据不一致。因此,字典类型在多线程环境中并不是线程安全的,因为它的操作不满足原子性,需要额外的同步机制(如锁)来保证在多线程环境下的正确使用。
那么对于 python 变量的赋值是原子性的吗?(refer)
Simple assignment to simple variables is "atomic" AKA threadsafe (compound assignments such as
+=
or assignments to items or attributes of objects need not be, but your example is a simple assignment to a simple, albeit global, variable, thus safe).
简单的赋值操作是原子性的,但对于组合的赋值操作就可能不是,可以看下面几个例子:
# L & L1 是列表,x 是对象,D & D1 是字典, i & j 是 int
L.append(x)
L1.extend(L2)
x = L[i]
x = L.pop()
L1[i:j] = L2
L.sort()
x = y
x.field = y
D[x] = y
D1.update(D2)
D.keys()
但是下面这些操作就不是原子性的
i = i+1
L.append(L[-1])
L[i] = L[j]
D[x] = D[x] + 1
怎么理解上面这两段的区别呢?可以使用 python 内置的 dis 模块输出函数执行的字节码,对比下下面两个函数的字节码:
忽略上面的 LOAD_FAST 和 LOAD_CONST, 可以看到第一种赋值操作只涉及一条指令的执行,这条指令执行是原子性的,所以这个赋值操作是线程安全的;而第二种赋值操作涉及两条指令,不满足原子性,那么这个操作就是线程不安全的。
所以扯这么多,其实不依赖于语言本身的实现,利用队列、锁、信号量来实现反而是更推荐的做法。当然也可以实现 ThreadSafeDict 的方式来达到目的,这里提供一段样例代码, 当然类似 get、update、__contain__ 这里并没有实现,读者可以自己补充
from collections.abc import MutableMapping
class ThreadSafeDict(MutableMapping):
def __init__(self, *args, **kwargs):
self._dict = dict(*args, **kwargs)
self._lock = Lock()
def __repr__(self):
return self._dict.__repr__()
def __getitem__(self, k):
with self._lock:
return self._dict[k]
def __setitem__(self, k, v):
with self._lock:
self._dict[k] = v
def __delitem__(self, k):
with self._lock:
del self._dict[k]
def __iter__(self):
with self._lock:
return self._dict.__iter__()
def __len__(self):
with self._lock:
return self._dict.__len__()