Python GIL

2018-12-03  本文已影响15人  DejavuMoments

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 算不得是低效的设计,其坏处在于锁的粒度太粗。

深入理解 GIL:如何写出高性能及线程安全的 Python 代码

上一篇 下一篇

猜你喜欢

热点阅读