摄影:产品经理甜白与草莓更配~
我刚到现在这个公司时,听到当时一个高级工程师(现已离职)大声地跟他旁边的同事说:
Python 有 GIL 锁,所以它的多线程实际上是单线程,所以写多线程代码不用考虑线程冲突,不用加锁。
”
相信现在看这篇文章的同学,不少人也是这样认为的。
然而,我要告诉你的是,这句话前半句是对的,后半句是 错的。Python 的多线程确实本质上是单线程。但你依然需要考虑线程并发冲突。
我们来举个例子:
场景 1:单线程
变量 a 的值为1
.代码读取 a 的值,把它加 1 变成2
.然后把2
重新赋值给 a。代码再次读取 a 的值,把它加 1 变成3
.然后重新把3
复制给 a。最终 a 的值是3
.
场景 2:伪多线程
Python 的多线程是伪多线程,意味着在微观上它是单线程。同一个时间,只有一个线程在运行,其他线程是暂停的状态。现在有一个变量 a,它里面的值为1
.
- 线程 1 从 a 里面获取到
1
,把它加1
,线程 1 还没有来得及把数字2
重新赋值给变量 a 时,时间片切换到线程 2. - 此时线程 1 暂停。线程 2 去读取变量 a,这个时候的 a 依然是
1
.线程 2 也把这个数字加1
,变成2
. - 时间片切换到线程 1,线程 2 暂停。线程 1 把数字
2
赋值给变量 a,此时 a 的值变成2
. - 时间片切换到线程 2,线程 1 暂停。线程 2 把数字
2
赋值给变量 a,此时 a 的值还是2
.
可以看到,即使同一时间只有一个线程在运行,但是两个线程同时修改同一个变量时,也会发生并发冲突。
GIL 到底锁的是什么?
大家都说 Python 有 GIL 锁,那么这个锁到底锁的是什么东西??
GIL 的全称是 Global Interpreter Lock, 全局解释器锁。它锁的是解释器而不是你的 Python 代码。它防止多线程同时执行 Python 的字节码(bytecodes),防止多线程同时访问 Python 的对象。
在 Python 官方文档Releasing the GIL from extension code[1]中,有这样一段话:
Here is how these functions work: the global interpreter lock is used to protect the pointer to the current thread state. When releasing the lock and saving the thread state, the current thread state pointer must be retrieved before the lock is released (since another thread could immediately acquire the lock and store its own thread state in the global variable). Conversely, when acquiring the lock and restoring the thread state, the lock must be acquired before storing the thread state pointer.
”
其中加黑的这一句话是说:GIL 锁用来保护指向当前进程状态的指针。
再看文档Thread State and the Global Interpreter Lock[2]中提到的这样一句话:
Without the lock, even the simplest operations could cause problems in a multi-threaded program: for example, when two threads simultaneously increment the reference count of the same object, the reference count could end up being incremented only once instead of twice.
”
当两个线程同时提高同一个对象的引用计数时,(如果没有 GIL 锁)那么引用计数只会被提高了 1 次而不是 2 次。
大家注意我这两段应用中的指针
和引用计数
。其中指针是 C 语言的概念,Python 没有指针;引用计数是 Python 底层的概念。你平时写的 Python 代码,引用计数是在你调用变量的时候自动增加的,不需要你去手动加 1.
所以 GIL 锁住的东西,都是不需要你的代码直接交互的东西。
Python 的解释器通过切换线程来模拟多线程并发的情况,如上面举的例子,虽然同一个时间只有一个线程在活动,但仍然可以导致并发冲突。
所以,以后不要再说出 Python 不需要解决并发冲突这种话了。