Python编程中Python与GIL互斥锁关系作用分析

我们知道,在 CPython 中,有一个全局解释器锁,英文叫 global interpreter lock,简称 GIL,是一个互斥锁,用来保护 Python 世界里的对象,防止同一时刻多个线程执行 Python 的字节码,从而确保线程安全,这导致了 Python 的线程无法利用多核 CPU 的优势,因此有人说 Python 的多线程是伪多线程,性能不高,那么 Python 将来有可能去除 GIL 吗?

要回答这个问题,先从 GIL 的起源进行分析。

GIL 的起源

Python 第一次发布是在 1991 年,当时的 CPU 都是单核,单核中,多线程主要为了一边做IO,一边做 CPU 计算而设计的,Python 编译器是由 C 语言编写的,因此也叫 CPython,那时候很多编程语言没有自动内存管理的功能,为了实现自动垃圾回收,Python 为每一个对象进行了引用计数,当引用计数为 0 的时候说明该对象可以回收,从而释放内存了,比如:

>>> import sys
>>> data = { 'gzh': 'Python七号'}
>>> var1 = data
>>> sys.getrefcount(data)
3
>>> 

这里 data 对象就有 3 个引用, 一个是本身,一个是变量 var1,一个是 getrefcount 函数的参数,如果此时又有一个线程引用了 data,那么引用计数再增加 1,如果某个线程使用了 data 后运行结束,那么引用计数就减少 1,多线程对同一个变量「引用计数」进行修改,就会遇到 race conditions(竞争),为了避免 race conditions,最简单有效的办法就是加一个互斥锁。

如果对每一个对象都加锁,有可能引发另一个问题,就是死锁,而且频繁的获取和释放会导致性能下降,最简单有效的方法就是加一个解释器锁,线程在执行任何字节码时都先获取解释器锁,这就避免了死锁,而且不会有太多的性能消耗。当时 CPU 都是单核,而且这种 GIL 设计简单,并不会影响性能,因此一直沿用至今天。GIL 存在最主要的原因,就是因为 Python 的内存管理不是线程安全的,这就是 GIL 产生并存在的主要缘由。

尝试消除 GIL

CPU 进入多核时代后,可以同时做多个计算任务, GIL 才真正变成问题。在 1999 年,有个叫 Greg Stein 的大佬基于 Python 1.5 版本消除了 GIL,取代代之的是在可变数据结构上加上更细粒度的锁,也提交了补丁用于去除对全局可变对象的依赖,然后在标准测试时表明去除 GIL 后单线程比不去除时慢了近 2 倍,测试的机器还是当时性能最好 Windows 机器。也就是说除去了 GIL 后,你使用 2 个 CPU 才能获取比原来 1 个 CPU 稍微好一点的性能,这种提升明显得不偿失,Greg Stein 的尝试也就失败告终。

Python 之父 Guido van Rossum 也欢迎社区的志愿者去尝试去除 GIL,只要不降低单线程的性能,但他也提到,去掉 GIL 不是一件容易的事。

Python 开发者邮件列表中也偶尔会有去除 GIL 的议题,但是以下需求必须满足:

  • 简单。从长远来看该方案必须是可实施、可维护的。
  • 并发。去除 GIL 必须能提升多线程的性能。
  • 速度。去除 GIL 不能降低单线程的性能。
  • 满足 CPython 的特性。该方案必须支持 CPython 的功能,比如 __del__ 和弱引用。
  • API 的兼容性。该方案应与所有现有CPython扩展使用的宏在源方面兼容。
  • 及时销毁不可达对象,回收内存。
  • 有序销毁,比如不可达对象 X 引用了 A,那么应该在销毁 A 之前先销毁 X(有些垃圾回收算法并不能做到这一点)。

有些需求不容易被满足,比如 4,5,7,目前,还没有人满足以上需求的同时去除 GIL 成功的。

积重难返

这些年 Python 实在太火了,很多优秀的库都是基于 CPython 进行编写的,很多都是 90 年代的 C 扩展库,如果要除去 GIL,那么很多基于 GIL 编写的 C 扩展便无法使用,也就是去了 GIL,Python 生态有很多扩展或三方库者无法使用。

还有一个很明显的例子,Python 解释器不止有 CPython,还有用 Java 编写的 Python,.NET 实现的 IronPython,这些解释器完全没有 GIL,可是有多少人为它们编写扩展呢?

Python 之所以如此火爆,与它有着丰富的三方库开箱即用有着很大的关系,积重难返,去除 GIL 很困难。

为什么 Python3 一开始时不去除 GIL

Python3 在最开始时是有机会实现很多新功能,在此过程中,打破了一些现有的 C 扩展,然后需要更新和移植更改以配合 Python 3,这也是 Python3 一开始不被社区所接受的原因。

与 Python2 相比,删除 GIL 将使 Python3 在单线程性能方面更慢,而且很多优秀的扩展将不能再使用,如果真的这样,可以想象 Python3 不可能有未来,最终的结果是 Python3 仍然保持有 GIL。

但 Python3 也为现有的 GIL 带来了重大改进,在 Python 3.2 版本中,确保了计算密集型线程和 I/O 密集型线程并存时, I/O 密集型长期获取不到 GIL 而无法执行的问题,提升了多线程的性能。

最后的话

Python 因为内存管理不是线程安全的,因此自出生起就自带 GIL,然后很多扩展都是在 GIL 的保护下编写的,时间一长积重难反,Python3 一开始也因去除 GIL 导致单线程性能下降的问题而保留 GIL,现在已经是 Python3.9 版本了,将来 Python 去除 GIL 的可能性微乎其微,换句话说,去除 GIL 的 Python 也就不是我们认识的 Python 了。

不过不必沮丧,GIL 影响的也仅仅是多线程执行计算密集型的任务罢了,这种场景大多数程序员都很少遇到,即使有,可以使用多进程来避免 GIL 的影响,或者使用其他编程语言实现,任何编程语言或技术都不是十全十美的,发挥所长是最重要的,即使有 GIL,我也不在乎,也会依然使用 Python。

以上就是Python与GIL互斥锁关系分析的详细内容,更多关于Python与GIL互斥锁的资料请关注我们其它相关文章!

(0)

相关推荐

  • 简要讲解Python编程中线程的创建与锁的使用

    创建线程 创建线程的两种方法: 1,直接调用threading.Thread来构造thread对象,Thread的参数如下: class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})  group为None: target为线程将要执行的功能函数: name为线程的名字,也可以在对象构造后调用setName()来设定: args为tuple类型的参数,可以为多个,如果只有一个也的使用tuple的形

  • 详解Python中的GIL(全局解释器锁)详解及解决GIL的几种方案

    先看一道GIL面试题: 描述Python GIL的概念, 以及它对python多线程的影响?编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因. GIL:又叫全局解释器锁,每个线程在执行的过程中都需要先获取GIL,保证同一时刻只有一个线程在运行,目的是解决多线程同时竞争程序中的全局变量而出现的线程安全问题.它并不是python语言的特性,仅仅是由于历史的原因在CPython解释器中难以移除,因为python语言运行环境大部分默认在CPython解释器中. 通过

  • Cpython解释器中的GIL全局解释器锁

    1.什么是GIL全局解释器锁 GIL:Global Interpreter Lock,意思就是全局解释器锁,这个GIL并不是Python的特性,他是只在Cpython解释器里引入的一个概念,而在其他的语言编写的解释器里就没有GIL,例如:Jython,Pypy等 下面是官方给出的解释: In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from exe

  • Python Threading 线程/互斥锁/死锁/GIL锁

    导入线程包 import threading 准备函数线程,传参数 t1 = threading.Thread(target=func,args=(args,)) 类继承线程,创建线程对象 class MyThread(threading.Thread) def run(self): pass if __name__ == "__main__": t = MyThread() t.start() 线程共享全面变量,但在共享全局变量时会出现数据错误问题 使用 threading 模块中的

  • Python编程中Python与GIL互斥锁关系作用分析

    我们知道,在 CPython 中,有一个全局解释器锁,英文叫 global interpreter lock,简称 GIL,是一个互斥锁,用来保护 Python 世界里的对象,防止同一时刻多个线程执行 Python 的字节码,从而确保线程安全,这导致了 Python 的线程无法利用多核 CPU 的优势,因此有人说 Python 的多线程是伪多线程,性能不高,那么 Python 将来有可能去除 GIL 吗? 要回答这个问题,先从 GIL 的起源进行分析. GIL 的起源 Python 第一次发布是

  • Python实现的多线程同步与互斥锁功能示例

    本文实例讲述了Python实现的多线程同步与互斥锁功能.分享给大家供大家参考,具体如下: #! /usr/bin/env python #coding=utf-8 import threading import time ''' #1.不加锁 num = 0 class MyThread(threading.Thread): def run(self): global num time.sleep(1) #一定要sleep!!! num = num + 1 msg = self.name + '

  • Python解析器Cpython的GIL解释器锁工作机制

    目录 本节重点 一 引子 二 GIL介绍 三 GIL与Lock 四 GIL与多线程 五 多线程性能测试 本节重点 掌握Cpython的GIL解释器锁的工作机制 掌握GIL与互斥锁 掌握Cpython下多线程与多进程各自的应用场景 本节时长需控制在45分钟内 一 引子 定义: In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing

  • python实现多进程并发控制Semaphore与互斥锁LOCK

    一.了解锁 应用场景举例描述: Lock 互斥锁:举例说明–有三个同事同时需要上厕所,但是只有一间厕所,将同事比作进程,多个进程并打抢占一个厕所,我们要保证顺序优先, 一个个来,那么就必须串行,先到先得,有人使用了,就锁住,结束后剩余的人继续争抢 1.利用Lock来处理 模拟三个同事抢占厕所 from multiprocessing import Process from multiprocessing import Lock import time import random def task

  • Python编程中的反模式实例分析

    本文实例讲述了Python编程中的反模式.分享给大家供大家参考.具体分析如下: Python是时下最热门的编程语言之一了.简洁而富有表达力的语法,两三行代码往往就能解决十来行C代码才能解决的问题:丰富的标准库和第三方库,大大节约了开发时间,使它成为那些对性能没有严苛要求的开发任务的首选:强大而活跃的社区,齐全的文档,也使很多编程的初学者选择了它作为自己的第一门编程语言.甚至有国外的报道称,Python已经成为了美国顶尖大学里最受欢迎的编程入门教学语言. 要学好一门编程语言实属不易,在初学阶段,就

  • Python编程中NotImplementedError的使用方法

    Python编程中raise可以实现报出错误的功能,而报错的条件可以由程序员自己去定制.在面向对象编程中,可以先预留一个方法接口不实现,在其子类中实现. 如果要求其子类一定要实现,不实现的时候会导致问题,那么采用raise的方式就很好. 而此时产生的问题分类是NotImplementedError. 写一段代码如下: class ClassDemo: def test_demo(self): raiseNotImplementedError("my test: not implemented!&

  • Python编程中*args与**kwargs区别作用详解

    相信学Python的小伙伴肯定有这样的尴尬局面,给一个函数不会用, 原因是:不知道参数列表中的类型是什么意思,比如初学者都会疑问的:*args和**kwargs到底是怎么用. 当你知道这个时,我猜你肯定能会用好多函数了! #*args的用法:当传入的参数个数未知,且不需要知道参数名称时. def func_arg(farg, *args): print("formal arg:", farg) for arg in args: print("another arg:"

  • Python编程中闭包的变量作用域问题解析

    目录 闭包 闭包中的变量 闭包 ​ 在我们使用返回函数的时候,由于我们在一个函数中需要返回另一个函数,因此,我们在这个函数中就需要重新定义一个函数.而这样,就造成了我们的函数嵌套问题.外面的函数相对于里面的函数而言是外函数(outer function),而里面的我们叫他内函数(inner function). def outerFunction(): #外函数 def innerFunction(): #内函数 x = 1 return x return innerFunction #返回值是

  • Python编程中非常重要却又被严重低估的库decorator

    目录 常规的装饰器 使用神库 带参数的装饰器 签名问题有解决? 总结一下 本文已经收录于<Python黑魔法手册>v2.1 版本,在线文档请前往 Python黑魔法手册 2.0 文档 这个库可以帮你做什么呢 ? 其实很简单,就是可以帮你更方便地写python装饰器代码,更重要的是,它让 Python 中被装饰器装饰后的方法长得更像装饰前的方法. 不了解装饰器的可以先去阅读我们之前的文章,非常全且详细的介绍了装饰器的各种实现方法. 常规的装饰器 下面这是一个最简单的装饰器示例,在运行 myfun

随机推荐