Python核心技术与实战
景霄
Facebook资深工程师
立即订阅
13891 人已学习
课程目录
已完结 46 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 从工程的角度深入理解Python
免费
基础篇 (14讲)
01 | 如何逐步突破,成为Python高手?
02 | Jupyter Notebook为什么是现代Python的必学技术?
03 | 列表和元组,到底用哪一个?
04 | 字典、集合,你真的了解吗?
05 | 深入浅出字符串
06 | Python “黑箱”:输入与输出
07 | 修炼基本功:条件与循环
08 | 异常处理:如何提高程序的稳定性?
09 | 不可或缺的自定义函数
10 | 简约不简单的匿名函数
11 | 面向对象(上):从生活中的类比说起
12 | 面向对象(下):如何实现一个搜索引擎?
13 | 搭建积木:Python 模块化
14 | 答疑(一):列表和元组的内部实现是怎样的?
进阶篇 (11讲)
15 | Python对象的比较、拷贝
16 | 值传递,引用传递or其他,Python里参数是如何传递的?
17 | 强大的装饰器
18 | metaclass,是潘多拉魔盒还是阿拉丁神灯?
19 | 深入理解迭代器和生成器
20 | 揭秘 Python 协程
21 | Python并发编程之Futures
22 | 并发编程之Asyncio
23 | 你真的懂Python GIL(全局解释器锁)吗?
24 | 带你解析 Python 垃圾回收机制
25 | 答疑(二):GIL与多线程是什么关系呢?
规范篇 (7讲)
26 | 活都来不及干了,还有空注意代码风格?!
27 | 学会合理分解代码,提高代码可读性
28 | 如何合理利用assert?
29 | 巧用上下文管理器和With语句精简代码
30 | 真的有必要写单元测试吗?
31 | pdb & cProfile:调试和性能分析的法宝
32 | 答疑(三):如何选择合适的异常处理方式?
量化交易实战篇 (8讲)
33 | 带你初探量化世界
免费
34 | RESTful & Socket: 搭建交易执行层核心
35 | RESTful & Socket: 行情数据对接和抓取
36 | Pandas & Numpy: 策略与回测系统
免费
37 | Kafka & ZMQ:自动化交易流水线
38 | MySQL:日志和数据存储系统
39 | Django:搭建监控平台
40 | 总结:Python中的数据结构与算法全景
技术见闻与分享 (4讲)
41 | 硅谷一线互联网公司的工作体验
42 | 细数技术研发的注意事项
加餐 | 带你上手SWIG:一份清晰好用的SWIG编程实践指南
43 | Q&A:聊一聊职业发展和选择
结束语 (1讲)
结束语 | 技术之外的几点成长建议
Python核心技术与实战
登录|注册

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

景霄 2019-07-01
你好,我是景霄。
前面几节课,我们学习了 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/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Python核心技术与实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(28)

  • 小侠龙旋风
    先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
    44
  • leixin
    老师,我曾经去某大厂面试。人家问了我几个问题,比说说,你知道元类吗?Python是如何解决循环引用的?换句话说,Python的垃圾回收机制是如何?我后来自己找了些资料看了,还是,不是理解的特别明白。老师后面的课程能帮我们讲解下吗?
    2019-07-01
    20
  • leixin
    有重要的一点没讲,GIL会在遇到io的时候自动释放,给其他线程执行的机会,这样Python多线程在io阻塞的多任务中有效。
    2019-07-01
    11
  • 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
    5
  • helloworld
    python的单线程和多线程同时都只能利用一颗cpu核心,对于纯cpu heavy任务场景,不涉及到io耗时环节,cpu都是充分利用的,多线程和单线程相比反倒是多了线程切换的成本,所以性能反而不如单线程。
    2019-07-01
    3
  • 程序员人生
    t1 = Thread(target=CountDown, args=[n // 2]) 老师,这段代码里面n//2是什么意思?
    2019-07-01
    6
    2
  • farFlight
    另外,在测试不加锁的 foo 函数的时候,我这里循环测试10000次也不会见到n!=100的情况,这是为什么呢?
    2019-07-01
    1
    2
  • Paul Shan
    GIL在Python作为脚本或者客户端程序没问题,作为高性能程序多少有点问题。当引入协程后,并发被很好处理,现在只剩下并行不太理想,用的是多进程模型,没有利用操作系统最小调度单元。
    2019-11-21
  • Paul Shan
    Python 多线程是伪多线程。
    2019-11-21
  • 建强
    思考题1:
    由于GIL采用轮流运行线程的机制,GIL需要在线程之间不断轮流进行切换,线程如果较多或运行时间较长,切换带来的性能损失可能会超过单线程。

    思考题2:
    个人觉得GIL仍然是一种好的设计,虽然损失了一些性能,但在保证资源不发生冲突,预防死锁方面还是有一定作用的。

    以上是个人的一点肤浅理解,请老师指正。
    2019-10-25
  • 自由民
    思考题
    1.线程切换有开销成本,另外最主要是由于GIL的存在使python的并行为伪并行。
    2.我觉得不是好设计,像戴着镣铐跳舞,好处仅仅是简化了解释器的设计。而且它并不能完全解决线程安全问题。但我平时很少用多线程编程,所以还没有实际体会。而像解释器这样的基础设施应该是把脏活留给自己,尽量减少用户的复杂性。类似还有从python2到python3的大变动。
    2019-10-15
  • 扶幽
    第一问:因为Python中有GIL的存在,使得在每一个时刻都只能有一个线程在运行。如果强行使用多线程的话,线程的切换回耗费额外的资源,所有运行更慢;
    第二问:有,使用Python编写的有限元数值程序运算很慢,而且当时也不懂什么线程安不安全,也不会加锁,最后算出来的结果怎么都跟标准答案对不上。
    :-P
    2019-10-13
  • Geek_54edc1
    1、线程切换对性能有消耗 2、GIL的设计方便了CPython的编写,但是对使用者不太友好
    2019-09-24
  • Hector
    我有个问题,多核cpu下的多线程比单核cpu下的多线程(假设一个进程)的效率是更低的,原因是线程颠簸。老师能解释下线程颠簸么?多核cpu下的多线程GIL是怎么释放的,其他线程是怎么争夺的?
    2019-09-19
  • Nick
    老师,想请教一下:文中在“Python 的线程安全”的一节中,以foo函数的字节码为例,说“这四行 bytecode 中间都是有可能被打断的”。我觉得,即使比如在某行bytecode执行完之后线程被打断了,等这个线程重新获得GIL后不是会继续接着执行后面的bytecode吗,因此从结果看,最终每一条bytecode都被执行完了,那么对结果应该是不会有影响的呀?
    2019-08-10
    1
  • taoist
    1. 线程切换需要成本(CPU资源) ,CPU密集场合使用多线程会更慢
    2. 刚开始是一个好设计,可以更快更简洁更高效的实现代码,后来就成了历史的遗留的包袱,有大量的库依赖GIL 带来的线程安全,废除的成本非常高,因此CPython 只能不断改进 GIL,很难一次性彻底取消。
    2019-07-25
  • 且听疯吟
    1. pyhton的多线程按照文中所说的实际上还是单线程的实现,表面上使用了python的多线程,但实际还是单线运行,相比较单线程而言还多了许多进程切换的锁的开销,因此多线程版本反而比单线程版本要慢。
    2019-07-17
  • 贺宇
    和ruby一样
    2019-07-15
  • Ben
    1. 多线程适合处理多个独立的子任务, 如果n是列表, 那么多线程/多进程可以大大减少执行时间. 但是针对单个数字n的计算, 多线程计算时变量n, 可以视为被竞争的资源, 会lock住非执行线程, GIL机制会check_interval, 强制更换为其他线程, 额外增加了执行时间. 形象来说, 就是一个人要干的活, 非要几个人一起干, 一个人干的时候. 其他人只能干看着, GIL换人时, 还要额外的时间.
    2. 每一种设计都是为了解决问题设计的, I/O慢时使用asyncio, 也就是GIL适合的场景. 但是I/O快时, 适合多线程, 此时GIL能去掉就好了
    2019-07-05
  • 汪zZ
    1.在第一例子中多线程间切换需要时间,所以多线程比单线程慢。
    2.关于思考题2,我最近在写一个GUI,左边需要实时显示摄像头,右边做一些常规处理。用的是tk,左边摄像头多线程显示,直接用多线程摄像头视频会闪,然后用tk的update更新摄像头显示,图像会稍微卡顿。
    能不能指点一下,怎么才能在界面上左边显示摄像头不卡不闪?
    2019-07-05
收起评论
28
返回
顶部