一、线程的生命周期(新建、就绪、运行、阻塞和死亡)

当线程被创建并启动以后,它既不是一启动就进入执行状态的,也不是一直处于执行状态的,在线程的生命周期中,它要经过新建(new)、就绪(Ready)、运行(Running)、阻塞(Blocked)和死亡(Dead)5 种状态。

尤其是当线程启动以后,它不可能一直“霸占”着 CPU 独自运行,所以 CPU 需要在多个线程之间切换,于是线程状态也会多次在运行、就绪之间转换。

线程的新建和就绪状态

当程序创建了一个 Thread 对象或 Thread 子类的对象之后,该线程就处于新建状态,和其他的Python 对象一样,此时的线程对象并没有表现出任何线程的动态特征,程序也不会执行线程执行体。

当线程对象调用 start() 方法之后,该线程处于就绪状态,Python 解释器会为其创建方法调用栈和程序计数器,处于这种状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于 Python 解释器中线程调度器的调度。

二、全局解释器锁

简单来说,Python全局解释器锁(Global Interpreter Lock)或GIL是一个互斥锁,它只允许一个线程来控制Python解释器。

这意味着在任何时间点只有一个线程可以处于执行状态。执行单线程程序的开发人员感受不到GIL的影响,但它可能是CPU限制型和多线程代码中的性能瓶颈。

由于即使在具有多个CPU核心的多线程架构中,GIL一次也只允许一个线程执行,因此GIL已经成为Python“臭名昭着”的特性。

在CPython中

IO密集型,某个线程阻塞,就会调度其他就绪线程;

CPU密集型,当前线程可能会连续的获得GIL,导致其它线程几乎无法使用CPU。

在CPython中由于有GIL存在,IO密集型,使用多线程较为合算;CPU密集型,使用多进程,要绕开GIL。

新版CPython正在努力优化GIL的问题,但不是移除。如果在意多线程的效率问题,请绕行,选择其它语言erlang、Go等。

为什么需要GIL

Python使用引用计数进行内存管理。这意味着在Python中创建的对象具有引用计数变量,该变量用于跟踪指向该对象的引用数。当此计数达到零时,释放对象占用的内存。

让我们看一个简短的代码示例来演示引用计数的工作原理:

>>>

>>> import sys

>>> a = []

>>> b = a

>>> sys.getrefcount (a)

3

在上面的示例中,空列表对象的引用计数为3。列表对象由a,b引用并且参数传递给sys.getrefcount()。

回到GIL:

问题是这个引用计数变量需要保护竞争条件。如果其中两个线程同时增加或减少其值,如果发生这种情况,它可能导致从未释放的内存泄漏,或者更糟糕的是,在对该对象的引用仍然存在时错误地释放内存。这可能会导致Python程序中出现崩溃或其他“怪异”错误。通过向跨线程共享的所有数据结构添加锁,可以保持此引用计数变量的安全性,从而不会对它们进行不一致的修改。

但是为每个对象或对象组添加一个锁意味着将存在多个锁,这可能导致另一个问题 - 死锁(死锁只有在有多个锁时才会发生)。另一个副作用是由于重复获取和释放锁而导致性能下降。

GIL是解释器本身的单个锁,它增加了一条规则,即执行任何Python字节码都需要获取解释器锁。这可以防止死锁(因为只有一个锁)并且不会引入太多的性能开销。但它有效地使任何受CPU限制的Python程序都是单线程的。

注意

需要注意的点包括:

第一,GIL 不属于 Python 语言定义,而是 CPython 解释器实现的一部分;

第二,其他 Python 解释器不一定有 GIL。例如 Jython (JVM) 和 IronPython (CLR) 没有 GIL,而 PyPy 有 GIL;

第三,GIL 并不是 Python 的专利。其他语言也有 GIL,尤其是动态语言,如 Ruby MRI。

在多线程环境中,Python 按以下方式执行:

a、设置 GIL;

b、切换到一个线程去运行;

c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));

d、把线程设置为睡眠状态;

e、解锁 GIL;

d、再次重复以上所有步骤。在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。

至于GIL,不要认为它在那的存在就是静态的和未经分析过的。Antoine Pitrou 在Python 3.2中实现了一个新的GIL,并且带着一些积极的结果。这是自1992年以来,GIL的一次最主要改变。这个改变非常巨大,很难在这里解释清楚,但是从一个更高层次的角度来说,旧的GIL通过对Python指令进行计数来确定何时放弃GIL。这样做的结果就是,单条Python指令将会包含大量的工作,即它们并没有被1:1的翻译成机器指令。在新的GIL实现中,用一个固定的超时时间来指示当前的线程以放弃这个锁。在当前线程保持这个锁,且当第二个线程请求这个锁的时候,当前线程就会在5ms后被强制释放掉这个锁(这就是说,当前线程每5ms就要检查其是否需要释放这个锁)。

然而,这并不是一个完美的改变。对于在各种类型的任务上有效利用GIL这个领域里,最活跃的研究者可能就是David Beazley了。除了对Python 3.2之前的GIL研究最深入,他还研究了这个最新的GIL实现,并且发现了很多有趣的程序方案。对于这些程序,即使是新的GIL实现,其表现也相当糟糕。他目前仍然通过一些实际的研究和发布一些实验结果来引领并推进着有关GIL的讨论。

为什么还没有删除GIL?

Python的开发人员对此有很多抱怨,但是像Python这样流行的语言不会带来像删除GIL那样重要的变化而不会导致向后不兼容问题。

显然可以删除GIL,过去开发人员和研究人员已多次执行此操作,但所有这些尝试都破坏了现有的C扩展,这些扩展在很大程度上依赖于GIL提供的解决方案。

当然,还有其他解决方案可以解决GIL解决的问题,但有些解决方案会降低单线程和多线程I / O绑定程序的性能,其中一些程序太难了。毕竟,你不希望现有的Python程序在新版本发布后运行得更慢,对吧?

Python的创建者和BDFL,Guido van Rossum,在2007年9月的文章给出了社区的答案:

“ 只有当单线程程序(以及多线程但 I / O绑定程序)的性能不降低时,我才欢迎使用Py3k中的一组补丁”

此后的任何尝试都没有实现这一条件。

GIL的解决方案

如果GIL导致您出现问题,可以尝试以下几种方法:

多进程与多线程:最流行的方法是使用多方法,使用多个进程而不是线程。

替代Python解释器:Python有多个解释器实现。分别用C,Java,C#和Python编写的CPython,Jython,IronPython和PyPy是最受欢迎的。GIL仅存在于CPython的原始Python实现中。如果您的程序及其库可用于其他实现之一,那么您也可以尝试它们。

所以没救了么?

当然Python社区也在非常努力的不断改进GIL,甚至是尝试去除GIL。并在各个小版本中有了不少的进步。

将切换颗粒度从基于opcode计数改成基于时间片计数

避免最近一次释放GIL锁的线程再次被立即调度

新增线程优先级功能(高优先级线程可以迫使其他线程释放所持有的GIL锁)

总结

Python GIL其实是功能和性能之间权衡后的产物,它尤其存在的合理性,也有较难改变的客观因素。从本分的分析中,我们可以做以下一些简单的总结:

因为GIL的存在,只有IO Bound场景下得多线程会得到较好的性能

如果对并行计算性能较高的程序可以考虑把核心部分也改成C模块,或者索性用其他语言实现

GIL在较长一段时间内将会继续存在,但是会不断对其进行改进

参考资料

原文:https://www.cnblogs.com/Nicholas0707/p/11279548.html

gopython 获取python 全局线程锁失败_Python之路(第四十三篇)线程的生命周期、全局解释器锁...相关推荐

  1. python 线程超时设置_python多任务之总结——(二)线程、协程

    本文主要讲述进程线程协程在python中的使用.主要说明各自的创建.通信及联系与区别,了解各自的适用场景,能更好的利用并发实现多任务开发. 第一部分:python多任务之总结--(一)进程学习 线程 ...

  2. python线程池并发_python 并发编程多线程之进程池/线程池

    一.验证GIL锁的存在 Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行.虽然 Python 解释器中可以"运行"多个线程,但在任意时刻只有一个线程在解释器中运行 ...

  3. python线程池模块_python并发编程之进程池,线程池,协程

    需要注意一下 不能无限的开进程,不能无限的开线程 最常用的就是开进程池,开线程池.其中回调函数非常重要 回调函数其实可以作为一种编程思想,谁好了谁就去掉 只要你用并发,就会有锁的问题,但是你不能一直去 ...

  4. python线程池模块_python并发编程之进程池,线程池,协程(Python标准模块--concurrent.futures(并发未来))...

    需要注意一下 不能无限的开进程,不能无限的开线程 最常用的就是开进程池,开线程池.其中回调函数非常重要 回调函数其实可以作为一种编程思想,谁好了谁就去掉 只要你用并发,就会有锁的问题,但是你不能一直去 ...

  5. python读取mp4文件失败_Python代码打开本地.mp4格式文件的方法-mp4文件

    Python开发技术的应用相信有不少的小伙伴都有所了解,简单的说那就是非常的强大,Python开发技术的应用是非常广泛的,本篇文章扣丁学堂Python培训小编就给读者们分享一下Python代码打开本地 ...

  6. python模板引擎传迭代器_python之路 模块,序列化,迭代器,生成器

    一.模块 1.模块简介 模块是一个包含所有你定义的函数和变量的文件,其后缀名是.py.模块可以被别的程序引入,以使用该模块中的函数等功能.这也是使用python标准库的方法. 类似于函数式编程和面向过 ...

  7. python中__init__导入失败_python - 如何使用__init__.py修复“在非包中尝试相对导入”...

    python - 如何使用__init__.py修复"在非包中尝试相对导入" 我正在尝试遵循PEP 328,具有以下目录结构: pkg/ __init__.py component ...

  8. 获取python脚本的返回值_Python多线程获取返回值代码实例

    这篇文章主要介绍了Python多线程获取返回值代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在使用多线程的时候难免想要获取其操作完的返回值 ...

  9. python字符串截取split 失败_python如何截断字符串

    字符串本质上就是由多个字符组成的,Python 允许通过索引来操作字符,比如获取指定索引处的字符,获取指定字符在字符串中的位置 等.python中截取字符串,可以使用split()方法,或者使用索引来 ...

  10. python爬虫登录12306失败_Python网络爬虫(selenium模拟登录12306网站)

    一.通过selenium自动登录12306官网 1.1 超级鹰打码平台API,创建chaojiyin.py文件 #!/usr/bin/env python#coding:utf-8 importreq ...

最新文章

  1. python入门之控制结构-循环结构_Python 入门之控制结构 - 循环结构(一)
  2. mysql 星期_MYSQL经典SQL之星期问题
  3. virtualbox和vagrant卸载脚本在macbook
  4. C#发布程序添加其他程序文件
  5. python设置window系统ip
  6. JavaScript之实例练习(正反选、二级联动)
  7. nginx ngx_http_upstream_module
  8. 炒股十余年,亏了很多钱,现在很迷茫是退出股市还是继续坚持?
  9. 程序员惊魂 12 小时:“���”引发线上事故
  10. springboot之全局处理异常封装
  11. dotween曲线运动 unity_Unity3D DOTween动画插件详解
  12. 看图纸V3.2.1正式版[看图纸正式版下载]
  13. 读写SharedPreferences中的数据
  14. 睡眠 应该用 a加权 c加权_困成狗?谈谈睡眠研究的遗传发现之旅
  15. 江苏省高等数学竞赛经验分享
  16. 信息安全-网络安全风险评估技术原理与应用(一)
  17. python文件处理基础_第六篇:python基础之文件处理
  18. ios 开发中遇到的一些问题
  19. 互联网大厂的后端技术栈
  20. Excel中VLOOKUP跨文件查找

热门文章

  1. Oracle merge into用法以及相关例子示例
  2. ArcGIS多面体(multipatch)解析——引
  3. [转帖]Android Bitmap内存限制OOM,Out Of Memory
  4. Spring AOP实现及运行期调用原理分析
  5. 据说这些基础知识90%的人都回答错了,你呢?
  6. Nginx之4包罗万象 - (虚拟主机)
  7. ieee trans pami latex模板
  8. Linux重定向和管道的基础学习
  9. apollocaffe编译问题
  10. 18.1---不用加号的加法(CC150)