GIL

GIL:global interpreter lock(cpython)
在python中,一个线程对应于c中的一个线程。
gil使得同一个时刻只有一个线程在CPU上执行字节码(python在执行的时候会将py文件编译成字节码)。
同时也预示着无法将多个线程映射到多个CPU上运行,无法体现多CPU的优势。
只要运行一个python进程,里面不管有多少个线程,他都只能运行在一个CPU上。
在其它静态语言里面可以将多个线程映射到多个CPU上。
因此,在python里面,并发就非常受限,python一直在努力的去GIL化。
但是短期内是无法实现的,因为大量的第三方包都是使用cpython来完成的。
当然也有其它的解释器,比如pypi去GIL化。

GIL锁为了线程运行安全,因为多个线程运行,尤其是运行同一段代码的时候,十分容易出错。
所以python在最初的时候就加了一把GIL锁,这使得同一时刻只有一个线程在CPU上执行字节码。
这样保证了某种程度上线程是安全的。python正是由于GIL锁使得多线程的效率不是很高。

虽然同一时刻只有一个线程运行在CPU上,那么是否就意味着编写多线程编码就是安全的了?
就不去考虑线程间的同步了?实际上不是的。
通过下面示例可以知道,GIL锁会在适当的时间释放掉,不会一直占有。

from threading import Thread
from multiprocessing import Process

a = 0
def add():
    global a
    b = 0
    for i in range(1000000):
        a += 1
        b += 1
    print("循环次数:",b)

def desc():
    global a
    c = 0
    for i in range(1000000):
        a -= 1
        c += 1
    print("循环次数:",c)

def task():
    t1 = Thread(target=add)
    t2 = Thread(target=desc)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(a)

if __name__ == "__main__":
    for i in range(4):
        p = Process(target=task)
        p.start()
        p.join()

如果按照代码的逻辑,那么最终打印的结果都为0。
执行结果:
循环次数: 1000000
循环次数: 1000000
-4652
循环次数: 1000000
循环次数: 1000000
468960
循环次数: 1000000
循环次数: 1000000
-61354
循环次数: 1000000
循环次数: 1000000
196468
我们发现每次的执行结果都不相同,
原因可能有二:
第一,两个任务没有做完,
第二,两个任务之间的变量相互影响,也就是GIL锁在途中被释放掉了。
我们通过打印循环次数说明任务都做完了,那么就只能是GIL锁在任务执行过程中被释放掉了,导致两个任务的a变量相互影响。

当把GIL这把锁交给一个线程之后,他不会等到线程执行完毕之后再释放。
它会根据字节码执行的行数以及时间片,然后把它释放出来。
另外,当GIL遇到IO操作的时候也会释放。也正是由于这个原因,GIL在IO操作频繁的时候非常适用。

posted @ 2019-08-02 16:32  明王不动心  阅读(321)  评论(0编辑  收藏  举报