关于我
编程界的一名小程序猿,目前在一个创业团队任team lead,技术栈涉及Android、Python、Java和Go,这个也是我们团队的主要技术栈。 联系:hylinux1024@gmail.com

0x00 什么是全局解析锁(GIL)

A global interpreter lock (GIL) is a mechanism used in computer-language interpreters to synchronize the execution of threads so that only one native thread can execute at a time. --引用自wikipedia

从上面的定义可以看出,GIL是计算机语言解析器用于同步线程执行的一种同步锁机制。很多编程语言都有GIL,例如PythonRuby

0x01 为什么会有GIL

Python作为一种面向对象的动态类型编程语言,开发者编写的代码是通过解析器顺序解析执行的。 大多数人目前使用的Python解析器是CPython提供的,而CPython的解析器是使用引用计数来进行内存管理,为了对多线程安全的支持,引用了global intepreter lock,只有获取到GIL的线程才能执行。如果没有这个锁,在多线程编码中即使是简单的操作也会引起共享变量被多个线程同时修改的问题。例如有两个线程同时对同一个对象进行引用时,这两个线程都会将变量的引用计数从0增加为1,明显这是不正确的。
可以通过sys模块获取一个变量的引用计数

>>> import sys
>>> a = []
>>> sys.getrefcount(a)
2
>>> b = a
>>> sys.getrefcount(a)
3
复制代码

sys.getrefcount()方法中的参数对a的引用也会引起计数的增加。

是否可以对每个变量都分别使用锁来同步呢?

如果有多个锁的话,线程同步时就容易出现死锁,而且编程的复杂度也会上升。当全局只有一个锁时,所有线程都在竞争一把锁,就不会出现相互等待对方锁的情况,编码的实现也更简单。此外只有一把锁时对单线程的影响其实并不是很大。

0x02 可以移除GIL吗?

Python核心开发团队以及Python社区的技术专家对移除GIL也做过多次尝试,然而最后都没有令各方满意的方案。

内存管理技术除了引用计数外,一些编程语言为了避免引用全局解析锁,内存管理就使用垃圾回收机制。

当然这也意味着这些使用垃圾回收机制的语言就必须提升其它方面的性能(例如JIT编译),来弥补单线程程序的执行性能的损失。
对于Python的来说,选择了引用计数作为内存管理。一方面保证了单线程程序执行的性能,另一方面GIL使得编码也更容易实现。
Python中很多特性是通过C库来实现的,而在C库中要保证线程安全的话也是依赖于GIL

所以当有人成功移除了GIL之后,Python的程序并没有变得更快,因为大多数人使用的都是单线程场景。

0x03 对多线程程序的影响

首先来GILIO密集型程序和CPU密集型程序的的区别。 像文件读写、网络请求、数据库访问等操作都是IO密集型的,它们的特点需要等待IO操作的时间,然后才进行下一步操作;而像数学计算、图片处理、矩阵运算等操作则是CPU密集型的,它们的特点是需要大量CPU算力来支持

对于IO密集型操作,当前拥有锁的线程会先释放锁,然后执行IO操作,最后再获取锁。线程在释放锁时会把当前线程状态存在一个全局变量PThreadState的数据结构中,当线程获取到锁之后恢复之前的线程状态

用文字描述执行流程

保存当前线程的状态到一个全局变量中
释放GIL
... 执行IO操作 ...
获取GIL
从全局变量中恢复之前的线程状态
复制代码

下面这段代码是测试单线程执行500万次消耗的时间

import timeCOUNT = 50000000def countdown(n):while n > 0:n -= 1start = time.time()
countdown(COUNT)
end = time.time()print('Time taken in seconds -', end - start)# 执行结果
# Time taken in seconds - 2.44541597366333
复制代码

在我的8核的macbook上跑大约是2.4秒,然后再看一个多线程版本

import time
from threading import ThreadCOUNT = 50000000def countdown(n):while n > 0:n -= 1t1 = Thread(target=countdown, args=(COUNT // 2,))
t2 = Thread(target=countdown, args=(COUNT // 2,))start = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
end = time.time()print('Time taken in seconds -', end - start)# 执行结果
# Time taken in seconds - 2.4634649753570557
复制代码

上文代码每个线程都执行250万次,如果线程是并发的,执行时间应该是上面单线程版本的一半时间左右,然而在我电脑中执行时间大约为2.5秒! 多线程不但没有更高效率,反而还更耗时了。这个例子就说明Python中的线程是顺序执行的,只有获取到锁的线程可以获取解析器的执行时间。多线程执行多出来的那点时间就是获取锁和释放锁消耗的时间。

那如何实现高并发呢?

答案是使用多进程。前面的文章有介绍多进程的使用

from multiprocessing import Pool
import timeCOUNT = 50000000def countdown(n):while n > 0:n -= 1if __name__ == '__main__':pool = Pool(processes=2)start = time.time()r1 = pool.apply_async(countdown, [COUNT // 2])r2 = pool.apply_async(countdown, [COUNT // 2])pool.close()pool.join()end = time.time()print('Time taken in seconds -', end - start)# 执行结果
# Time taken in seconds - 1.2389559745788574
复制代码

使用多进程,每个进程运行250万次,大约消耗1.2秒的时间。差不多是上面线程版本的一半时间。

当然还可以使用其它Python解析器,例如JythonIronPythonPyPy

既然每个线程执行前都要获取锁,那么有一个线程获取到锁一直占用不释放,怎么办?

IO密集型的程序会主动释放锁,但对于CPU密集型的程序或IO密集型和CPU混合的程序,解析器将会如何工作呢?
早期的做法是Python会执行100条指令后就强制线程释放GIL让其它线程有可执行的机会。
可以通过以下获取到这个配置

>>> import sys
>>> sys.getcheckinterval()
100
复制代码

在我的电脑中还打印了下面的输出警告

Warning (from warnings module):File "__main__", line 1
DeprecationWarning: sys.getcheckinterval() and sys.setcheckinterval() are deprecated.  Use sys.getswitchinterval() instead.
复制代码

意思是sys.getcheckinterval()方法已经废弃,应该使用sys.getswitchinterval()方法。 因为传统的实现中每解析100指令的就强制线程释放锁的做法,会导致CPU密集型的线程会一直占用GILIO密集型的线程会一直得不到解析的问题。于是新的线程切换方案就被提出来了

>>> sys.getswitchinterval()
0.005
复制代码

这个方法返回0.05秒,意思是每个线程执行0.05秒后就释放GIL,用于线程的切换。

0x04 总结

CPython解析器的实现由于global interpreter lock(全局解释锁)的存在,任何时刻都只有一个线程能执行Pythonbytecode(字节码)。
常见的内存管理方案有引用计数和垃圾回收,Python选择了前者,这保证了单线程的执行效率,同时对编码实现也更加简单。想要移除GIL是不容易的,即使成功将GIL去除,对Python的来说是牺牲了单线程的执行效率。
PythonGILIO密集型程序可以较好的支持多线程并发,然而对CPU密集型程序来说就要使用多进程或使用其它不使用GIL的解析器。
目前最新的解析器实现中线程每执行0.05秒就会强制释放GIL,进行线程的切换。

0x05 为了看懂GIL我阅读了下面这些资料

  • docs.python.org/3.7/glossar…
    GIL
  • docs.python.org/3.7/glossar…
    bytecode字节码
  • github.com/python/cpyt…
    CPython 源码
  • wiki.python.org/moin/Global…
  • realpython.com/python-gil/
    What is the Python Global Interpreter Lock (GIL)?
  • dabeaz.blogspot.com/2010/01/pyt…
  • mail.python.org/pipermail/p…
  • www.youtube.com/watch?v=Obt…
    Understanding the Python GIL
  • en.wikipedia.org/wiki/Global…

转载于:https://juejin.im/post/5cf6313ee51d457756536751

你是否真的了解全局解析锁(GIL)相关推荐

  1. 深入理解Python中的全局解释锁GIL

    深入理解Python中的全局解释锁GIL 转自:https://zhuanlan.zhihu.com/p/75780308 注:本文为蜗牛学院资深讲师卿淳俊老师原创,首发自公众号https://mp. ...

  2. Python进阶并发基础--线程,全局解释器锁GIL由来,如何更好的利用Python线程,

    全局解释器锁GIL 官方对于线程的介绍: 在 CPython 中,由于存在全局解释器锁,同一时刻只有一个线程可以执行 Python代码(虽然某些性能导向的库可能会去除此限制).如果你想让你的应用更好地 ...

  3. 【Python核心】全局解释器锁GIL

    Python多线程另一个很重要的话题--GIL(Global Interpreter Lock,即全局解释器锁)鲜有人知,甚至连很多Python老司机都觉得GIL就是一个谜 一.一个不解之谜 耳听为虚 ...

  4. Python培训教程:什么是Python全局解释器锁(GIL)?

    本期Python培训教程小编为大家带来的是关于"什么是Python全局解释器锁(GIL)?"的问题,全局解释器锁是计算机程序设计语言解释器用于同步线程的工具,使得在同一进程内任何时 ...

  5. Python全局解释器锁GIL与多线程

    Python中如果是 I/O密集型的操作,用多线程(协程Asyncio.线程Threading),如果I/O操作很慢,需要很多任务/线程协同操作,用Asyncio,如果需要有限数量的任务/线程,那么使 ...

  6. 问:为什么python中有了全局解释器锁GIL,还要有互斥锁?

    首先我们在进行对比之前,我们要知道什么是全局解释器锁,和什么是互斥锁,他们分别是用来做什么的才能解决这个问题. 首先介绍全局解释解释器锁GIL,Python代码的执行由Python 虚拟机(也叫解释器 ...

  7. 【Python爬虫学习笔记11】Queue线程安全队列和GIL全局解释器锁

    Queue线程安全队列 在Python多线程编程中,虽然threading模块为我们提供了Lock类和Condition类借助锁机制来处理线程并发执行,但在实际开发中使用加锁和释放锁仍是一个经常性的且 ...

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

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

  9. python GIL 全局解释器锁详解

    Python多线程另一个很重要的话题--GIL(Global Interpreter Lock,即全局解释器锁)鲜有人知,甚至连很多Python老司机都觉得GIL就是一个谜 一.一个不解之谜 耳听为虚 ...

最新文章

  1. 大开眼界!AI在医疗和汽车行业的11个有趣应用
  2. [Warning] TIMESTAMP with implicit DEFAULT value is
  3. 【tmos】SpringBoot登录拦截
  4. 英特尔中国祝贺高亭宇夺冠:至强CPU提供更精准训练支持
  5. Integer的自动拆装箱的陷阱(整型数-128到127的值比较问题)
  6. latex hyperref_Latex 用subfig引用子图显示括号
  7. 将我人生的第一篇博客献给伟大的软件工程这门课程
  8. java+jxls利用excel模版进行导出
  9. arguments的类型是Array吗?
  10. 网络工程师的python之路pdf下载_网络工程师的Python之路:网络运维自动化实战
  11. 系统同传软件_影视翻译软件可实时在线翻译多国语言
  12. java计算机毕业设计实验室耗材管理系统源码+系统+数据库+lw文档+mybatis+运行部署
  13. VCPKG 常用命令
  14. 2021-02-19
  15. java 1.5.0 gcj_java gcj调试
  16. HTML5UI横向排列,5个实用的UI排版技巧,让你的作品更细致
  17. Android源码编译过程及刷机过程详解
  18. Dilated Convolution + Receptive Field
  19. 饿了么官宣合作抖音后,美团的失意是什么?
  20. [记录][问题]Win32调用C++/WinRT DLL

热门文章

  1. 2021年人工智能数据采集标注行业四大趋势预测
  2. BERT模型超酷炫,上手又太难?请查收这份BERT快速入门指南
  3. 【转】人脸识别功能的用户体验设计优化
  4. 想转行ML/AI却没有方向?这篇指南告诉你!
  5. 在AI领域每月投资一次,全面解析腾讯的人工智能奇招
  6. 前沿速递:因果涌现在多种因果衡量标准下普遍存在
  7. 群体决策是如何误入歧途的
  8. 科技热点思考:元宇宙发展及其风险挑战
  9. 数学史上的哲学绝唱——无穷观与数学基础的争论
  10. 你以为美国商业航天那么牛只是因为马斯克?更多原因在这里!