Python 核心技术与实战
景霄
Facebook 资深工程师
114324 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 47 讲
开篇词 (1讲)
Python 核心技术与实战
15
15
1.0x
00:00/00:00
登录|注册

23 | 你真的懂Python GIL(全局解释器锁)吗?

提供了绕过GIL的两种思路
适度剖析了GIL的实现原理
了解GIL对应用的影响
把关键性能代码放到别的语言(一般是C++)中实现
使用C实现的Python库不受GIL影响
如果代码不需要CPython解释器来执行,就不再受GIL的限制
仍需要注意线程安全,使用lock等工具确保线程安全
GIL并不意味着不用考虑线程安全
CPython解释器会轮流执行Python线程,模拟并行的线程
每个Python线程在执行时都会锁住自己的线程,阻止别的线程执行
避免复杂的竞争风险问题和因C语言库不是原生线程安全的问题
GIL是全局解释器锁,类似操作系统的Mutex
GIL导致Python线程的性能并不像期望的那样
怀疑Python的线程失效
使用多线程反而让运行变慢,总共花了9.6s
单线程执行CountDown(n)耗时5.4s
总结
如何绕过GIL?
Python的线程安全
GIL是如何工作的?
为什么有GIL?
一个不解之谜
Python GIL(全局解释器锁)

该思维导图由 AI 生成,仅供参考

你好,我是景霄。
前面几节课,我们学习了 Python 的并发编程特性,也了解了多线程编程。事实上,Python 多线程另一个很重要的话题——GIL(Global Interpreter Lock,即全局解释器锁)却鲜有人知,甚至连很多 Python“老司机”都觉得 GIL 就是一个谜。今天我就来为你解谜,带你一起来看 GIL。

一个不解之谜

耳听为虚,眼见为实。我们不妨先来看一个例子,让你感受下 GIL 为什么会让人不明所以。
比如下面这段很简单的 cpu-bound 代码:
def CountDown(n):
while n > 0:
n -= 1
现在,假设一个很大的数字 n = 100000000,我们先来试试单线程的情况下执行 CountDown(n)。在我手上这台号称 8 核的 MacBook 上执行后,我发现它的耗时为 5.4s。
这时,我们想要用多线程来加速,比如下面这几行操作:
from threading import Thread
n = 100000000
t1 = Thread(target=CountDown, args=[n // 2])
t2 = Thread(target=CountDown, args=[n // 2])
t1.start()
t2.start()
t1.join()
t2.join()
我又在同一台机器上跑了一下,结果发现,这不仅没有得到速度的提升,反而让运行变慢,总共花了 9.6s。
我还是不死心,决定使用四个线程再试一次,结果发现运行时间还是 9.8s,和 2 个线程的结果几乎一样。
这是怎么回事呢?难道是我买了假的 MacBook 吗?你可以先自己思考一下这个问题,也可以在自己电脑上测试一下。我当然也要自我反思一下,并且提出了下面两个猜想。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Python GIL(全局解释器锁)对Python并发编程产生重要影响。本文通过实例展示了GIL的影响,解释了GIL的作用和原因。GIL是CPython解释器中的全局解释器锁,阻止多个Python线程同时执行,导致线程并不像期望的那样并行。文章深入浅出地介绍了Python GIL的工作原理,以及为什么Python的线程仍需要考虑线程安全。尽管GIL限制了多线程并行执行,但并不意味着程序就可以高枕无忧,仍需要注意线程安全。文章提供了绕过GIL的两种思路,强调在大部分情况下并不需要过多考虑GIL。最后,留下了两道思考题,引发读者对GIL的思考和讨论。 文章通过实例和技术细节深入浅出地介绍了Python GIL,对于想要深入了解Python并发编程的读者具有很高的参考价值。同时,提到了绕过GIL的两种思路,为读者提供了解决GIL限制的方法。文章还留下了两道思考题,引发读者对GIL的思考和讨论。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Python 核心技术与实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(36)

  • 最新
  • 精选
  • 建强
    思考题1: 由于GIL采用轮流运行线程的机制,GIL需要在线程之间不断轮流进行切换,线程如果较多或运行时间较长,切换带来的性能损失可能会超过单线程。 思考题2: 个人觉得GIL仍然是一种好的设计,虽然损失了一些性能,但在保证资源不发生冲突,预防死锁方面还是有一定作用的。 以上是个人的一点肤浅理解,请老师指正。

    作者回复: 讲的不错

    2019-10-25
    2
    13
  • jackstraw
    关于绕过GIL的第二个方式:将关键的性能代码放到别的语言中(通常C++)实现;这种解决方式指的是在别的语言中使用多线程的方式处理任务么?就是不用python的多线程,而是在别的语言中使用多线程?

    作者回复: 对

    2020-01-15
    9
  • 小侠龙旋风
    先mark一下学到的知识点: 一、查看引用计数的方法:sys.getrefcount(a) 二、CPython引进GIL的主要原因是: 1. 设计者为了规避类似内存管理这样的复杂竞争风险问题(race condition); 2. CPython大量使用C语言库,但大部分C语言库都不是线程安全的(线程安全会降低性能和增加复杂度)。 三、绕过GIL的两种思路: 1. 绕过CPython,使用JPython等别的实现; 2. 把关键性能代码放到其他语言中实现,比如C++。 问答老师的问题: 1. cpu-bound属于计算密集型程序,用多线程运行时,每个线程在开始执行时都会锁住GIL、执行完会释放GIL,这两个步骤比较费时。相比单线程就没有切换线程的问题,所以更快。 相反,在处理多阻塞高延迟的IO密集型程序时,因为多线程有check interval机制,若遇阻塞,CPython会强制当前线程让出(释放)GIL,给其他线程执行的机会。所以能提高程序的执行效率。 2. 第二个问题摘抄了知乎上的讨论: 在python3中,GIL不使用ticks计数,改为使用计时器(执行时间达到阈值后interval=15毫秒,当前线程释放GIL),这样对CPU密集型程序更加友好,但依然没有解决GIL导致的同一时间只能执行一个线程的问题,所以效率依然不尽如人意。多核多线程比单核多线程更差,原因是单核下多线程,每次释放GIL,唤醒的那个线程都能获取到GIL锁,所以能够无缝执行,但多核下,CPU0释放GIL后,其他CPU上的线程都会进行竞争,但GIL可能会马上又被CPU0拿到,导致其他几个CPU上被唤醒后的线程会醒着等待到切换时间后又进入待调度状态,这样会造成线程颠簸(thrashing),导致效率更低。 经常会听到老手说:“python下想要充分利用多核CPU,就用多进程”,原因是什么呢?原因是:每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行,所以在python中,多进程的执行效率优于多线程(仅仅针对多核CPU而言)。所以我们能够得出结论:多核下,想做并行提升效率,比较通用的方法是使用多进程,能够有效提高执行效率。
    2019-07-06
    2
    144
  • leixin
    有重要的一点没讲,GIL会在遇到io的时候自动释放,给其他线程执行的机会,这样Python多线程在io阻塞的多任务中有效。
    2019-07-01
    3
    41
  • leixin
    老师,我曾经去某大厂面试。人家问了我几个问题,比说说,你知道元类吗?Python是如何解决循环引用的?换句话说,Python的垃圾回收机制是如何?我后来自己找了些资料看了,还是,不是理解的特别明白。老师后面的课程能帮我们讲解下吗?
    2019-07-01
    1
    34
  • helloworld
    python的单线程和多线程同时都只能利用一颗cpu核心,对于纯cpu heavy任务场景,不涉及到io耗时环节,cpu都是充分利用的,多线程和单线程相比反倒是多了线程切换的成本,所以性能反而不如单线程。
    2019-07-01
    10
  • SCAR
    1.cpu-bound任务的多线程相比单线程,时间的增加在于锁添加的获取和释放的开销结果。 2.返回到python诞生的年代,GIL相对来说是合理而且有效率的,它易于实现,很容易就添加到python中,而且它为单线程程序提供了性能提升。以至于Guido在“It isn't Easy to Remove the GIL”里面说“ I'd welcome a set of patches into Py3k only if the performance for a single-threaded program (and for a multi-threaded but I/O-bound program) does not decrease”。而到现在为止,任何尝试都没有达到这一条件。
    2019-07-01
    1
    8
  • farFlight
    另外,在测试不加锁的 foo 函数的时候,我这里循环测试10000次也不会见到n!=100的情况,这是为什么呢?
    2019-07-01
    2
    4
  • 张巡
    这是mackbook被黑的最惨的一次,哈哈哈
    2020-09-13
    3
  • Kfreer
    1在我们处理 cpu-bound 的任务(文中第一个例子)时,为什么有时候使用多线程会比单线程还要慢些? 答:由于CPython中GIL的存在(运行线程前需要先获取GIL),所以即便是多线程运行,同一时刻也只能有一个线程处于运行状态,且切线程之间切换时还要消耗一部分资源。这就导致cpu密集型任务下多线程反而没有单线程运行的快。 2你觉得 GIL 是一个好的设计吗?事实上,在 Python 3 之后,确实有很多关于 GIL 改进甚至是取消的讨论,你的看法是什么呢?你在平常工作中有被 GIL 困扰过的场景吗? 答:不是一个好的设计,仅仅是简化了解释器的设计,且并没有解决线程安全的问题。 总结:CPU密集型任务用多进程并行处理(CPU需多核),I/O密集型任务用协程,理论上协程比多线程的效率还要高。
    2020-05-26
    2
收起评论
显示
设置
留言
36
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部