python之GIL(全局解释锁)讲解

python之GIL(全局解释锁)讲解

GIL的官方解释:在cpython中,全局解释器锁(GIL)是一个互斥体,用于防止同时执行python字节码的本机线程。这个锁主要是必要的因为cpython的内存管理不是线程安全的。(然而,自从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。

为什么会有GIL

在python开发的早期,那时的cpu但是单核心的,当时的cpu厂商都在核心的频率上面下功夫。随着计算机硬件的发展,为了更加高效的利用多核处理器的性能,就出现了多线程编程的方式,而随之带来的是线程之间数据一致性和状态同步的困难。但是为了利用多核,Python开始支持多线程,而解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁。于是就有了GIL这把大锁,而当越来越多的代码库开发者接受了这种设定后,他们开始大量依赖这种特性(即默认python内部对象是thread-safe的,无需在实现时考虑额外的内存锁和同步操作)。慢慢的这种实现方式被发现是蛋疼且低效的。但当大家试图去拆分和去除GIL的时候,发现大量库代码开发者已经重度依赖GIL而非常难以去除了。有多难?做个类比,像MySQL这样的“小项目”为了把Buffer Pool Mutex这把大锁拆分成各个小锁也花了从5.5到5.6再到5.7多个大版为期近5年的时间,本且仍在继续。MySQL这个背后有公司支持且有固定开发团队的产品走的如此艰难,那又更何况Python这样核心开发和代码贡献者高度社区化的团队呢?

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

了解并行和并发

并行:多个CPU同时执行多个任务,就好像有两个程序,这两个程序是真的在两个不同的CPU内同时被执行。

并发:CPU交替处理多个任务,还是有两个程序,但是只有一个CPU,会交替处理这两个程序,而不是同时执行,只不过因为CPU执行的速度过快,而会使得人们感到是在“同时”执行,执行的先后取决于各个程序对于时间片资源的争夺。

注释:并行和并发属于多任务,目的是提高CPU的使用效率。一个CPU永远不可能实现并行,也就是说一个CPU不能同时运行多个程序,但是可以在随机分配的时间片内交换执行(并发)。

GIL的执行流程

如果多个线程的target=work,那么执行流程是

多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行

解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没有什么高明的方法,就是加锁处理,如下图的GIL,保证python解释器同一时间只能执行一个任务的代码
[外链图片转存失败(img-w4wdzxU2-1566565264623)(F:\my_blog\专门存放在博客中使用到的图片\GIL流程.png)]
GIL与Lock锁

相同点:都是相同的锁,互斥锁。

不同点:

  • GIL全局解释器锁,保护计时器内部的数据资源的安全,GIL锁上锁和释放无需手动操作。

  • 自己代码中定义的互斥锁保护进程中的资源数据的安全,自定义的互斥锁必须自己手动上锁,释放锁。
    在这里插入图片描述
    GIL影响性能
    [外链图片转存失败(img-yICErmLh-1566565264626)(F:\my_blog\专门存放在博客中使用到的图片\GIL的性能问题.jpg)]
    上图表示两个线程在双核CPU上执行情况。两个线程均为CPU密集型运算线程,绿色部分表示线程的云信,且在执行有用的计算,红色部分为线程调度唤醒,但是无法获得GIL导致无法进行有效运行等待的时间。GIL的存在导致多线程无法很好的立即多核CPU的并发处理能力。
    [外链图片转存失败(img-ieXFTKqZ-1566565264627)(F:\my_blog\专门存放在博客中使用到的图片\IO密集型.jpg)]
    上图是IO密集型线程,白色的部分代表IO线程处于等待,可见,当IO线程收到数据包引起终端切换后,仍然由于一个CPU密集型线程存在,导致无法获GIL锁,从而进行无尽的循环等待。

总结:Python的多线程在多核CPU上,只对于IO密集型计算产生正面效果;而当有至少有一个CPU密集型线程存在,那么多线程效率会由于GIL而大幅下降。

如何避免受GIL的影响

使用multiprocess代替Thread

multiprocess库的出现很大程度上是为了弥补thread库因为GIL而低效的缺陷。它完整的复制了一套thread所提供的接口方便迁移。唯一的不同就是它使用了多进程而不是多线程。每个进程有自己的独立的GIL,因此也不会出现进程之间的GIL争抢。

当然multiprocess也不是万能良药。它的引入会增加程序实现时线程间数据通讯和同步的困难。就拿计数器来举例子,如果我们要多个线程累加同一个变量,对于thread来说,申明一个global变量,用thread.Lock的context包裹住三行就搞定了。而multiprocess由于进程之间无法看到对方的数据,只能通过在主线程申明一个Queue,put再get或者用sharememory的方法。这个额外的实现成本使得本来就非常痛苦的多线程程序编码,变得更加痛苦了。

其他解释器

既然GIL是基于Cpython的产物,那么其他的解释器是不是更好呢?是这样的,比如JPython和Pypy以及IronPython这样的解释器由于语言的特性,他们不需要GIL的帮助。

面试题

  • 什么时候会释放Gil锁?

    • 遇到像 i/o操作这种 会有时间空闲情况 造成cpu闲置的情况会释放Gil。

    • 会有一个专门ticks进行计数 一旦ticks数值达到100 这个时候释放Gil锁 线程之间开始竞争Gil锁(说明:ticks这个数值可以进行设置来延长或者缩减获得Gil锁的线程使用cpu的时间)。

  • 互斥锁和Gil锁的关系?

    • Gil锁 : 保证同一时刻只有一个线程能使用到cpu。

    • 互斥锁 : 多线程时,保证修改共享数据时有序的修改,不会产生数据修改混乱。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值