请问大家有推荐的 CPython 相关教程或书籍吗?我希望找一套讲解 Python 虚拟机和 Python 编译器源码的教程学习。
我们再举个例子看看非原子操作下,怎么保证线程安全。
>>> n = 0
>>> def foo():
... global n
... n += 1
...
>>> import dis
>>> dis.dis(foo)
3 0 LOAD_GLOBAL 0 (n)
3 LOAD_CONST 1 (1)
6 INPLACE_ADD
7 STORE_GLOBAL 0 (n)
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
代码编译后的字节码指令:
语句 n += 1
被编译成了前 4 个字节码,后两个字节码是 foo 函数的 return 操作,解释器自动添加。
我们在上文提到,Python2 的线程每执行 1000 个字节码就会被动的让出 GIL。现在假如字节码指令 INPLACE_ADD
就是那第 1000 条指令,这时本应该继续执行 STORE_GLOBAL 0 (n)
存储到 n 地址的数据就被驻留在了堆栈中。如果同一时刻,变量 n 被别的处理器当前线程中的代码调用了。那么请问现在的 n 还是 +=1 之后的 n 吗?答案是此时的 n 发生了更新丢失,在两个当前线程中的 n 已经不是同一个 “n” 了。这就是上面我们提到过的内存可见性数据安全问题的又一个佐证。
下面的代码正确输出为 100,但在 Python 多线程多处理器场景中,可能会得到 99 或 98 的结果。
import threading
n = 0
threads = []
def foo():
global n
n += 1
for i in range(100):
t = threading.Thread(target=foo)
threads.append(t)
for t in threads:
t.start()
for t in threads:
t.join()
print(n)
此时,Python 程序员应该要想到使用 Python 线程库的锁来解决为。
import threading
n = 0
lock = threading.Lock()
threads = []
def foo():
global n
with lock:
n += 1
for i in range(100):
t = threading.Thread(target=foo)
threads.append(t)
for t in threads:
t.start()
for t in threads:
t.join()
print(n)
显然,即便 Python 已经存在了 GIL,但依旧要求程序员坚持 “始终为共享可变状态的读写上锁”。