1.什么是 GIL?
GIL 全称 Global Interpreter Lock,全局解释器锁,为 CPython 最为人诟病的地方之一。
GIL 允许我们一次只有一个线程运行在一个 CPU 上执行字节码。
GIL 为了线程运行安全,加了一把非常大的锁,不过这也使得多线程执行效率不高。无法发挥多核心的优势,并发性能非常的受限。
无法将多个线程映射到多个 CPU 上
但是!
import threading
total = 0
def add():
global total
for i in range(1000000):
total += 1
def desc():
global total
for i in range(1000000):
total -= 1
thread1 = threading.Thread(target=add)
thread2 = threading.Thread(target=desc)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(total)
输出结果并不会一样。
可以看到,GIL 不会一直占有。会释放的,然后另外的线程便可以执行。
那么什么情况下会释放 GIL ?
会根据执行的字节码行数以及时间片来释放 GIL
或者遇到 IO 操作的时候主动释放 GIL,这属于 Python 内部的策略问题。
协同式多任务处理
当一项任务比如网络 I/O启动,而在长的或不确定的时间,没有运行任何 Python 代码的需要,一个线程便会让出GIL,从而其他线程可以获取 GIL 而运行 Python。这种礼貌行为称为协同式多任务处理,它允许并发;多个线程同时等待不同事件。
抢占式多任务处理
Python线程可以主动释放 GIL,也可以先发制人抓取 GIL 。
让我们回顾下 Python 是如何运行的。你的程序分两个阶段运行。首先,Python文本被编译成一个名为字节码的简单二进制格式。第二,Python解释器的主回路,一个名叫 pyeval_evalframeex() 的函数,流畅地读取字节码,逐个执行其中的指令。
当解释器通过字节码时,它会定期放弃GIL,而不需要经过正在执行代码的线程允许,这样其他线程便能运行:
for (;;) {
if (--ticker < 0) {
ticker = check_interval;
/* Give another thread a chance */
PyThread_release_lock(interpreter_lock);
/* Other threads may run now */
PyThread_acquire_lock(interpreter_lock, 1);
}
bytecode = *next_instr++;
switch (bytecode) {
/* execute the next instruction ... */
}
}
默认情况下,检测间隔是1000 字节码。所有线程都运行相同的代码,并以相同的方式定期从他们的锁中抽出。在 Python 3 GIL 的实施更加复杂,检测间隔不是一个固定数目的字节码,而是15 毫秒。然而,对于你的代码,这些差异并不显著。
Python GIL其实是功能和性能之间权衡后的产物,它尤其存在的合理性,也有较难改变的客观因素。从本分的分析中,我们可以做以下一些简单的总结:
- 因为GIL的存在,只有IO Bound场景下得多线程会得到较好的性能
- 如果对并行计算性能较高的程序可以考虑把核心部分也成C模块,或者索性用其他语言实现
- GIL在较长一段时间内将会继续存在,但是会不断对其进行改进
其实 GIL 算不得是低效的设计,其坏处在于锁的粒度太粗。
网友评论