7.3.6 - GIL

Posted 如果迎着风就飞,俯瞰这世界有多美

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了7.3.6 - GIL相关的知识,希望对你有一定的参考价值。

GIL是什么

首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL。

那么CPython实现中的GIL又是什么呢?GIL全称Global Interpreter Lock为了避免误导,我们还是来看一下官方给出的解释:

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

大致翻译是:

在CPython中,全局解释器锁( GIL )是一个互斥体,它阻止多个本机线程同时执行Python字节码。这个锁是必要的,主要是因为CPython的内存管理不是线程安全的。(但是,自从GIL存在以来,其他功能也越来越依赖于它实施的保证。) )

为什么会有GIL

由于物理上得限制,各CPU厂商在核心频率上的比赛已经被多核所取代。为了更有效的利用多核处理器的性能,就出现了多线程的编程方式,而随之带来的就是线程间数据一致性和状态同步的困难。即使在CPU内部的Cache也不例外,为了有效解决多份缓存之间的数据同步时各厂商花费了不少心思,也不可避免的带来了一定的性能损失。

Python当然也逃不开,为了利用多核,Python开始支持多线程。而解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁。 于是有了GIL这把超级大锁,而当越来越多的代码库开发者接受了这种设定后,他们开始大量依赖这种特性(即默认python内部对象是thread-safe的,无需在实现时考虑额外的内存锁和同步操作)。

慢慢的这种实现方式被发现是蛋疼且低效的。但当大家试图去拆分和去除GIL的时候,发现大量库代码开发者已经重度依赖GIL而非常难以去除了。有多难?做个类比,像mysql这样的“小项目”为了把Buffer Pool Mutex这把大锁拆分成各个小锁也花了从5.5到5.6再到5.7多个大版为期近5年的时间,本且仍在继续。MySQL这个背后有公司支持且有固定开发团队的产品走的如此艰难,那又更何况Python这样核心开发和代码贡献者高度社区化的团队呢?

所以简单的说GIL的存在更多的是历史原因。如果推到重来,多线程的问题依然还是要面对,但是至少会比目前GIL这种方式会更优雅。

 如果并发的多个任务是计算密集型:多进程效率高

from multiprocessing import Process
from threading import Thread
import os,time
def work():
    res=0
    for i in range(100000000):
        res*=i


if __name__ == \'__main__\':
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(4):
        # p=Process(target=work) #耗时5s多
        p=Thread(target=work) #耗时18s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print(\'run time is %s\' %(stop-start))
4
run time is 31.451799154281616
运行结果
from multiprocessing import Process
from threading import Thread
import os,time
def work():
    res=0
    for i in range(100000000):
        res*=i


if __name__ == \'__main__\':
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(4):
        p=Process(target=work) #耗时15s多
        # p=Thread(target=work) #耗时31s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print(\'run time is %s\' %(stop-start))
4
run time is 15.402880907058716
运行结果

从上可以看出计算密集型,适合多进程、

 

如果并发的多个任务是I/O密集型:多线程效率高

from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
    time.sleep(2)
    # print(\'===>\')

if __name__ == \'__main__\':
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(400):
        p=Process(target=work) #耗时41s多,大部分时间耗费在创建进程上
        # p=Thread(target=work) #耗时2s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print(\'run time is %s\' %(stop-start))
4
run time is 41.38236689567566
运行结果
from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
    time.sleep(2)
    # print(\'===>\')

if __name__ == \'__main__\':
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(400):
        # p=Process(target=work) #耗时41s多,大部分时间耗费在创建进程上
        p=Thread(target=work) #耗时2s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print(\'run time is %s\' %(stop-start))
4
run time is 2.046117067337036
运行结果

从上可以看出IO密集型,适合多线程

 

应用:

多线程用于IO密集型,如socket,爬虫,web
多进程用于计算密集型,如金融分析

 

以上是关于7.3.6 - GIL的主要内容,如果未能解决你的问题,请参考以下文章

5.1.16 GIL锁

在 C++ 代码中发布 Python GIL

python中的GIL

GIL 相关 和进程池

GIL全局解释器锁

并发编程——GIL全局解释器锁死锁现象与递归锁信号量Event事件线程queue