Python线程前置知识点-GIL全局解析器
关于python多线程处理并发的话,这里就不得不再提一次GIL,之前也有提过关于这个GIL的一下简单介绍,这里也为了完整性,这里也复述一下。

因为GIL的存在,python中的多线程,是伪线程。
我们的知道我们的线程执行的是可以如果是在多核的的情况下,是可以分配到多核上进行执行的。如:java的多线程如果是在多核的情况下是可以分别在多核上进行执行的。
但是python的因为历史原因,久久未能把这个历史遗留的问题给处理,估计后续也不会处理了!
但是对于GIL的认知,需要梳理清楚几个问题:
1:什么是GIL?
说简单点就是,因为我们的python内存管理机制采用了引用计数的方式来处理垃圾回收,为了相关统计引用,以及方便进行数据管理,相关的Cpython解析器,规定:某个线程的执行,必须先要拿到GIL(全局解释锁)。
当我们的一个python程序跑起来之后,就会形成一个python程序进程,这个进程纸只有且只有一个GIL,在这个进程中的线程,如果需要执行,需要获取先拿到GIL 全局解释器锁。
如,一个四线程的程序,跑在一个四核的CPU上的话:

因为python调用操作系统的底层C语音的原生接口,就只是C的原生线程,在都多线程的情况在修改数据的时候,为了防止数据改乱了,在出口做了设置,把多线程执行变为串行处理,也就是所谓的全局解释器锁,保证了数据统一和安全。
PS:GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念,是相关python解析器所有。
所以,解释器加锁以后:
线程的执行其实是串行,或(微观上的并发执行),GIL上锁后意味着同一时间只有一个CPU在处理你的线程。
2.为什么需要引入GIL:
官方的说法是:
在cpython中,gil是一个互斥锁,用来保护对python对象的访问,防止多个线程同时执行python字节码。
在CPython的线程是操作系统的原生线程,在Linux的pthread完全由操作系统调度执行。pthread本身不是线程安全的,所以如果需要进行保证数据一致性,需要使用者通过锁来实现保证多线程的安全运行,因此CPython解释器下的Python实现多线程也必然存在线程不安全的问题。
所以,多线程下执行python字节码的情况下,所以加GIL这个锁(然而,由于GIL的存在,其他新增的特性,为了省事都在默认线程安全的前提下进行,导致GIL成了不可或缺的依赖。)
所以从官网说辞可以知道是引入的主要原因历史遗留原因:
1:首先并发变成的需求是,随着CPU发展而来的。所以我们的多线程编程也是是随着多核CPU发展而发展,但早起我们的cpython都是基于都是在单核CPU的前提下设计的,没考虑过多核的情况。
2:cpython在并发的需求情况下,为了利用多核,Python也开始支持多线程,但是它的内存管理不是线程安全的,为了解决多线程之间数据完整性和状态同步的,最简单方法自然就是加锁,为了更好进行内存更好的进行管理所以只能加GIL这个全局锁。
3.Python解释器有哪些?
Jython 一种由java语言编写的python解释器,是将python编译成Java字节码然后执行的一种解释器,可以方便地和Java的类库进行交互。
IronPython 将Python代码解释为.Net平台上运行的字节码进行执行,类似Jython解释器,可以方便的和.Net平台上的类库进行交互。
IPython
在交互效果上有所增强,但执行过程和功能方面和CPython是一样的。
PyPy
一种使用JIT(just-in-time)技术的编译器,专注于执行速度,对Python代码进行动态编译,从而提高Python的执行速度。
PS:PyPy在处理python代码的过程中,一小部分功能的处理和CPython的执行结果是有差异的,如果用PyPy建议先了解下PyPy和CPython的区别。
CPython
CPython是主流版本的解释器,这个解释器是使用C语言编写的,也是使用最为广泛的解释器,可以方便地和C/C++的类库进行交互。
4.CPython解释器执行代码过程是怎么样流程?
Python是一门动态语言,它是边解释边执行的
程序员写的源代码 在运行时需先被转换成字节码.cpy 然后再转换为机器码 CPU执行机器码。
整个过程就是:执行过程中:源代码-->字节码-->机器码,整个过程由是Python解释器处理完成的。
早期的GIL的执行机制是:
计数器+I/O操作阻塞+部分指令比较简单的不用计数 常规情况下数到100就释放锁,让其他线程有机会执行 另外如果遇到阻塞情况,也要提前释放锁让其他线程有机会执行 部分的指令不累加到计数里面
后期的GIL的执行机制是:
原因是:
多核情况下多线程执行时,计数器模式存在缺陷,当一个线程在CPU-1执行完之后释放GIL之后,存在一定的概率,又被自己把锁给抢夺回来
而此时其他CPU线程可能被唤醒后,进行竞争的申请锁,发现锁还在别的线程那,浪费了,并且有可能会一直得不到锁的情况。
多核CPU频繁进行线程切换.
按执行照字节码指令数来统计,有些指令码简单执行快,,有些很复杂,导致计数也存在缺陷
升级后:
去除计数的方式,改为使用时间片的方式(每个线程的执行时间片是5000微秒)左右。 新增一个锁实现强制线程切换,避免保证释放GIL后,又被自己马上又抢到。
5:那是不是我们的多线程就没任何意义了?
通常我们对于任务的执行分:IO密集型和CPU计算密集型。
PS:CPU性能的具体作用的体现:
在I/O密集型的程序中,CPU性能好坏无法决定执行的速度。 在CPU密集型的程序中,CPU性能好坏决定执行的速度,算力速度的比较!
而对于任务作业的这些我们的执行程序执行通常有两种状态:
阻塞:遇到IO,CPU需等待IO完成 非阻塞:遇到IO,CPU切换,不需要等待IO完成,后续再进行根据情况切换回来。
6:GIL锁和线程锁执行流程图示

通常我们的python中的线程如果遇到的IO的时候,会自动的进行GIL锁就的释放,让给其他的线程执行。
7:GIL锁和线程互斥锁的区别:
GIL不能保证自己开启的线程安全,但能保证解释器中的数据是线程安全。(嫌贵与解锁器对象而言)
GIL 在线程调用解释器时,会进行自动加锁,而在线程遇到IO阻塞时或线程代码执行完毕时会进行自动解锁。
线程互斥锁- 是相对于程序内部的相关的数据对象而言,锁的是程序内部操作的资源对象。
GIL锁的python字节码执行码
8:如何解决GIL?
如果无并发需求,忽略即可 分析需求,如相关的IO密集型任务,使用多线程也是可以提升到一定效率滴。 再不行就搬出来multiprocess(多进程)替代Thread可供选择。
multiprocess库的出现很大程度上是为了弥补thread库因为GIL而低效的缺陷。
它完整的复制了一套thread所提供的接口方便迁移。 每个进程有自己的独立的GIL,因此也不会出现进程之间的GIL争抢,通过多进程的方式绕过GIL。
再不行使用协程(用户态线程)-(单进程单线程模式) 通过编写C语言扩展与Python交互,在C语言层面绕过GIL实现多核利用。
个人其他博客地址
简书:https://www.jianshu.com/u/d6960089b087
掘金:https://juejin.cn/user/2963939079225608
小钟同学 | 文 【原创】| QQ:308711822
1:本文相关描述主要是个人的认知和见解,如有不当之处,还望各位大佬指正。 2:关于文章内容,有部分内容参考自互联网整理,如有链接会声明标注;如没有及时标注备注的链接的,如有侵权请联系,我会立即删除处理哟。




