Python中GIL的使用详解

 更新时间:2018年10月03日 09:12:29   作者:Harvard_Fly  
GIL的全称为Global Interpreter Lock,全局解释器锁。本篇文章详细的介绍了Python中GIL的使用,有需要了解Python中GIL用法的朋友可参考。希望此文章对各位有所帮助

1、GIL简介

GIL的全称为Global Interpreter Lock,全局解释器锁。

1.1 GIL设计理念与限制

python的代码执行由python虚拟机(也叫解释器主循环,CPython版本)来控制,python在设计之初就考虑到在解释器的主循环中,同时只有一个线程在运行。即在任意时刻只有一个线程在解释器中运行。对python虚拟机访问的控制由全局解释锁GIL控制,正是这个锁来控制同一时刻只有一个线程能够运行。

在调用外部代码(如C、C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于期间没有python的字节码运行,所以不会做线程切换)。

在python中使用都是操作系统级别的线程,linux中使用的pthread,window使用的是其原生线程。

从上面的概述中可以直观的看出py在同一时刻只能跑一个线程,这样在跑多线程的情况下,只有当线程获取到全局解释器锁后才能运行,而全局解释器锁只有一个,因此即使在多核的情况下也只能发挥出单核的功能。

那么这样看起来py不给力啊,GIL直接导致CPython不能利用物理多核的性能加速运行。那么为什么会有这样的设计?考虑到Guido van Rossum 在创造python的时候,上世纪90年代,多核cpu完全属于不可想象的,现在由于硬件发展速度太快,程序编写就要考虑用尽cpu的全部性能,否则就要被淘汰,那么对于python同样也要如此。

上面主要说的是这种设计的劣势,下面再讨论它的优势。

GIL的设计简化了CPython的实现,使得对象模型,包括关键的内建类型如字典,都隐式可以并发访问。锁住全局解释器使得其比较容易的实现对多线程的支持,但也折损了多处理器主机的并行计算能力。

但是不论标准的,还是第三方的扩展模块,都被设计成在进行密集计算任务时释放GIL。另外还有在做IO操作时,GIL总是被释放。对所有面对内建的操作系统C代码的程序来说,GIL会在这个IO调用之前被释放,以允许其它的线程在等待这个IO的时候运行。如果是纯计算的程序,没有IO操作,解释器会每隔100次或每隔一定时间15ms去释放GIL。

这里可以理解为IO密集型的python比计算密集型的程序更能利用多线程环境带来的便利。

1.2 GIL对线程执行的影响

多线程环境中,python虚拟机按照以下方式执行:

  1. 设置GIL
  2. 切换到一个线程去执行
  3. 运行代码,这里有两种机制:
    1. 指定数量的字节码指令(100个)
    2. 固定时间15ms线程主动让出控制
  4. 把线程设置为睡眠状态
  5. 解锁GIL
  6. 再次重复以上步骤

上节说到python语言和程序一样要考虑用尽cpu的性能,下面在讨论py的应对方法。

python的应对方法很简单,在新的python3中依然有GIL,原因大概有下几点:

  • CPython的GIL本意是用来保护所有全局的解释器和环境状态变量的,如果去掉GIL,就需要更多的更细粒度的锁对解释器的众多全局状态进行保护。或者采用Lock-Free算法。无论采用哪一种,要做到多线程安全都会比维系一个GIL要难得多。另外改动的还是CPython的代码树及其各种第三方扩展也在依赖GIL。
  • 进一步说,有人做过测试将GIL去掉,加入更细粒度的锁。但是实践检测对单线程来说,性能更低。只有利用的物理cpu到一定数目后,性能才会比GIL版本好。且现在绝大部分的python程序都是单线程的。

然后最重要的还在于以下几个方面,简单来说就是py不改,一样能实现我们的需求。

  • 自2.6引出的多进程标准库mutilprocessing,让多进程的python编写简化到类似多线程的程度,大大减轻GIL带来的诸多不利。
  • 利用ctypes绕过GIL:ctypes可以使py直接调用任意的C动态库的导出函数。所要做的只是用ctypes写python代码即可。而且,ctypes会在调用C函数前释放GIL。

python中GIL使得同一个时刻只有一个线程在一个cpu上执行,无法将多个线程映射到多个cpu上执行,但GIL并不会一直占有,它会在适当的时候释放

import threading
count = 0
def add():
  global count
  for i in range(10**6):
    count += 1

def minus():
  global count
  for i in range(10**6):
    count -= 1

thread1 = threading.Thread(target=add)
thread2 = threading.Thread(target=minus)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(count)

分别运行三次的结果:

-59452
60868
-77007

可以看到count并不是一个固定值,说明GIL会在某个时刻释放,那么GIL具体在什么情况下释放呢:

1.执行的字节码行数到达一定阈值

2.通过时间片划分,到达一定时间阈值

3.在遇到IO操作时,主动释放

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

相关文章

  • python快速排序代码实例

    python快速排序代码实例

    这篇文章主要介绍了python快速排序的代码实例,大家参考使用吧
    2013-11-11
  • Python二进制数据结构Struct的具体使用

    Python二进制数据结构Struct的具体使用

    在C/C++语言中,struct被称为结构体。而在Python中,struct是一个专门的库,用于处理字节串与原生Python数据结构类型之间的转换。本文就详细介绍struct的使用方式
    2021-06-06
  • python实现ipsec开权限实例

    python实现ipsec开权限实例

    这篇文章主要介绍了python实现ipsec开权限的方法,弥补了windows自带的命令行工具netsh ipsec static add filter不支持批量添加及添加重复规则的不足,非常具有实用价值,需要的朋友可以参考下
    2014-11-11
  • Django异步任务之Celery的基本使用

    Django异步任务之Celery的基本使用

    这篇文章主要给大家介绍了关于Django异步任务之Celery使用的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Django具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-03-03
  • python障碍式期权定价公式

    python障碍式期权定价公式

    这篇文章主要为大家详细介绍了python障碍式期权定价公式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-07-07
  • python爬虫控制aiohttp并发数量方式

    python爬虫控制aiohttp并发数量方式

    这篇文章主要介绍了python爬虫控制aiohttp并发数量方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-06-06
  • Python利用逻辑回归分类实现模板

    Python利用逻辑回归分类实现模板

    这篇文章主要介绍了Python利用逻辑回归分类实现模板的思路,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-02-02
  • 使用Django启动命令行及执行脚本的方法

    使用Django启动命令行及执行脚本的方法

    今天小编就为大家分享一篇使用Django启动命令行及执行脚本的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-05-05
  • Python字符串hashlib加密模块使用案例

    Python字符串hashlib加密模块使用案例

    这篇文章主要介绍了Python字符串hashlib加密模块使用案例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • Python3.7黑帽编程之病毒篇(基础篇)

    Python3.7黑帽编程之病毒篇(基础篇)

    这篇文章主要介绍了Python3.7黑帽编程之病毒篇(基础篇),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02

最新评论