通过前面的学习,我们了解了 Pyton 并发编程的特性以及什么是多线程编程。其实除此之外,Python 多线程还有一个很重要的知识点,就是本节要讲的 GIL。GIL,中文译为全局解释器锁。在讲解 GIL 之前,首先通过一个例子来直观感受一下 GIL 在 Python 多线程程序运行的影响。首先运行如下程序:

import timestart = time.clock()def CountDown(n):    while n > 0:        n -= 1CountDown(100000)print("Time used:",(time.clock() - start))

运行结果为:

Time used: 0.0039529000000000005

在我们的印象中,使用多个(适量)线程是可以加快程序运行效率的,因此可以尝试将上面程序改成如下方式:

import timefrom threading import Threadstart = time.clock()def CountDown(n):    while n > 0:        n -= 1t1 = Thread(target=CountDown, args=[100000 // 2])t2 = Thread(target=CountDown, args=[100000 // 2])t1.start()t2.start()t1.join()t2.join()print("Time used:",(time.clock() - start))

运行结果为:

Time used: 0.006673

可以看到,此程序中使用了 2 个线程来执行和上面代码相同的工作,但从输出结果中可以看到,运行效率非但没有提高,反而降低了。

如果使用更多线程进行尝试,会发现其运行效率和 2 个线程效率几乎一样(本机器测试使用 4 个线程,其执行效率约为 0.005)。这里不再给出具体测试代码,有兴趣的读者可自行测试。

是不是和你猜想的结果不一样?事实上,得到这样的结果是肯定的,因为 GIL 限制了 Python 多线程的性能不会像我们预期的那样。那么,什么是 GIL 呢?GIL 是最流程的 CPython 解释器(平常称为 Python)中的一个技术术语,中文译为全局解释器锁,其本质上类似操作系统的 Mutex。GIL 的功能是:在 CPython 解释器中执行的每一个 Python 线程,都会先锁住自己,以阻止别的线程执行。当然,CPython 不可能容忍一个线程一直独占解释器,它会轮流执行 Python 线程。这样一来,用户看到的就是“伪”并行,即 Python 线程在交替执行,来模拟真正并行的线程。有读者可能会问,既然 CPython 能控制线程伪并行,为什么还需要 GIL 呢?其实,这和 CPython 的底层内存管理有关。CPython 使用引用计数来管理内容,所有 Python 脚本中创建的实例,都会配备一个引用计数,来记录有多少个指针来指向它。当实例的引用计数的值为 0 时,会自动释放其所占的内存。举个例子,看如下代码:

>>> import sys
>>> a = []
>>> b = a
>>> sys.getrefcount(a)
3

可以看到,a 的引用计数值为 3,因为有 a、b 和作为参数传递的 getrefcount 都引用了一个空列表。假设有两个 Python 线程同时引用 a,那么双方就都会尝试操作该数据,很有可能造成引用计数的条件竞争,导致引用计数只增加 1(实际应增加 2),这造成的后果是,当第一个线程结束时,会把引用计数减少 1,此时可能已经达到释放内存的条件(引用计数为 0),当第 2 个线程再次视图访问 a 时,就无法找到有效的内存了。所以,CPython 引进 GIL,可以最大程度上规避类似内存管理这样复杂的竞争风险问题。

Python GIL底层实现原理

图 1 GIL 工作流程示意图

上面这张图,就是 GIL 在 Python 程序的工作示例。其中,Thread 1、2、3 轮流执行,每一个线程在开始执行时,都会锁住 GIL,以阻止别的线程执行;同样的,每一个线程执行完一段后,会释放 GIL,以允许别的线程开始利用资源。读者可能会问,为什么 Python 线程会去主动释放 GIL 呢?毕竟,如果仅仅要求 Python 线程在开始执行时锁住 GIL,且永远不去释放 GIL,那别的线程就都没有运行的机会。其实,CPython 中还有另一个机制,叫做间隔式检查(check_interval),意思是 CPython 解释器会去轮询检查线程 GIL 的锁住情况,每隔一段时间,Python 解释器就会强制当前线程去释放 GIL,这样别的线程才能有执行的机会。注意,不同版本的 Python,其间隔式检查的实现方式并不一样。早期的 Python 是 100 个刻度(大致对应了 1000 个字节码);而 Python 3 以后,间隔时间大致为 15 毫秒。当然,我们不必细究具体多久会强制释放 GIL,读者只需要明白,CPython 解释器会在一个“合理”的时间范围内释放 GIL 就可以了。整体来说,每一个 Python 线程都是类似这样循环的封装,来看下面这段代码:

for (;;) {    if (--ticker < 0) {        ticker = check_interval;           /* Give another thread a chance */        PyThread_release_lock(interpreter_lock);        /* Other threads may run now */           PyThread_acquire_lock(interpreter_lock, 1);    }    bytecode = *next_instr++;    switch (bytecode) {        /* execute the next instruction ... */    }}

从这段代码中可以看出,每个 Python 线程都会先检查 ticker 计数。只有在 ticker 大于 0 的情况下,线程才会去执行自己的代码。

Python GIL不能绝对保证线程安全

注意,有了 GIL,并不意味着 Python 程序员就不用去考虑线程安全了,因为即便 GIL 仅允许一个 Python 线程执行,但别忘了 Python 还有 check interval 这样的抢占机制。比如,运行如下代码:

import threadingn = 0def foo():    global n    n += 1threads = []for i in range(100):    t = threading.Thread(target=foo)    threads.append(t)for t in threads:    t.start()for t in threads:    t.join()print(n)

执行此代码会发现,其大部分时候会打印 100,但有时也会打印 99 或者 98,原因在于 n+=1 这一句代码让线程并不安全。如果去翻译 foo 这个函数的字节码就会发现,它实际上是由下面四行字节码组成:

>>> import dis
>>> dis.dis(foo)
LOAD_GLOBAL              0 (n)
LOAD_CONST               1 (1)
INPLACE_ADD
STORE_GLOBAL             0 (n)

而这四行字节码中间都是有可能被打断的!所以,千万别以为有了 GIL 程序就不会产生线程问题,我们仍然需要注意线程安全。

解释为脑瘫的那张图_Python GIL全局解释器锁详解(深度剖析)相关推荐

  1. python解释器的工作原理_Python GIL全局解释器锁详解(深度剖析)

    通过前面的学习,我们了解了 Pyton 并发编程的特性以及什么是多线程编程.其实除此之外,Python 多线程还有一个很重要的知识点,就是本节要讲的 GIL. GIL,中文译为全局解释器锁.在讲解 G ...

  2. 云渲染一张图贵吗?渲染问题详解

    我们知道使用云渲染可以个高效快捷的渲染工程项目文件,节省很多时间,广受CG行业人员的喜爱,而且也相信有很多小伙伴经常会有疑问:云渲染一张图多钱?今天,就随云渲染小编一起来了解清楚这些问题... 一.云 ...

  3. python全局解释锁_Python GIL 全局解释性锁介绍

    什么是GIL GIL (Global Interpreter Lock),全局解释性锁,它上锁的对象是解释器,而Python代码的运行需要解释器进行解释成字节码并提供虚拟机运行,这么大粒度的锁意味着, ...

  4. cp解释为脑瘫的那张图_脑瘫英语简称,cp是什么意思网络用语脑瘫。

    cp的意思是脑瘫吗 CP,cerebral palsy,脑性瘫痪(简称脑瘫). 第一到第三十的英语和缩写,前面为缩写,后面为全拼 第一: 1st first 第二 :2nd second 第三 :3r ...

  5. python每隔30s检查一次_Python的全局解释器锁

    Python的全局解释器锁 全局解释器锁(GlobalInterpreter Lock,缩写GIL),是解释器同步线程的一种机制,它使得任何时刻仅有一个线程在执行. 即便在多核心处理器上,使用GIL  ...

  6. python3d动态图-Python图像处理之gif动态图的解析与合成操作详解

    本文实例讲述了Python图像处理之gif动态图的解析与合成操作.分享给大家供大家参考,具体如下: gif动态图是在现在已经司空见惯,朋友圈里也经常是一言不合就斗图.这里,就介绍下如何使用python ...

  7. python 全局解释器锁_python全局解释器锁(GIL)

    什么是全局解释器锁GIL 首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念.就好比C++是一套语言(语法)标准,但是可以用不同的编译 ...

  8. 多图上传以及多图排序的方法及流程详解

    多图上传以及多图排序的方法及流程详解 ps:本人亲测,阿里云2核4G5M的服务器性价比很高,新用户一块多一天,老用户三块多一天,最高可以买三年,感兴趣的可以戳一下:阿里云折扣服务器 所用插件包打包下载 ...

  9. 3d网上渲染平台是怎么渲图的_云渲染流程详解!

    题主说的看到许多网友对''3d网上渲染平台是怎么渲图的''进行提问,瑞云渲染小编也提供自己的小小见解.针对3D网上渲染平台是指什么,实际应该是指云渲染农场.几十年来,随着计算机软硬件不断更迭,图形图像 ...

最新文章

  1. Yii-mongo操作
  2. Edison与Arduino通过USB对接通信
  3. java grizzly_java grizzly实现http服务器
  4. 机器学习经典算法之线性回归sklearn实现
  5. python opencv 窗口循环显示时,如果用鼠标拖动窗口会导致程序暂停(卡住)(不知道为啥。。。)
  6. word-vba-microsoft(中英文)
  7. mariadb与mysql的兼容_「MySQL架构」MariaDB versus MySQL: Compatibility
  8. (转)jquery基础教程七 选择器(selectors 的xpath语法应用)
  9. 自增ID有什么坏处?什么样的场景下不使用自增ID?
  10. python opencv视频流_python – PyQt显示来自opencv的视频流
  11. 关于android studio报错Attempt to invoke virtual method 'void android.widget.ListView.setAdapter(android.
  12. 使用NanoHttpd实现简易WebServer
  13. (收藏)《博客园精华集》ASP.NET分册
  14. 人民币对澳元汇率的大数据分析与预测
  15. 橙色云再度入选中国科学院发布的《互联网周刊》
  16. 小红帽linux各功能中英,英文短剧lbrack;小红帽rsqb;剧本台词完整版---中英对照文本版...
  17. [原创]我眼中的乔布斯
  18. 【HDU5442】 Favorite Donut(后缀数组)
  19. 【JDBC】JPA和JDBC的区别
  20. 小孔成像总结_每天一个小实验|小孔成像

热门文章

  1. linux安装Git依赖的包出错,技术|Linux有问必答:如何在Linux上安装Git
  2. 2019年终总结-2020展望「持续更新至31号」
  3. RabbitMQ六种队列模式-主题模式
  4. Android 起调系统功能,打开系统浏览器,拨打电话,发送短信,手机震动,跳转到设置通知开关页面
  5. docker 目录 挂载
  6. mongodb 高级查询 统计记录条数
  7. 超链接js点击后页面向上滚动问题解决
  8. java搭建tcp客户端_【Java学习笔记】TCP客户端/服务器端
  9. Abstract 的使用
  10. 通过mvn dependency:tree 查看依赖树,解决依赖jar冲突问题