一 介绍
官方定义:
在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
GIL 的功能是:在 CPython 解释器中执行的每一个 Python 线程,都会先锁住自己,以阻止别的线程执行。
首先需要明确的一点是GIL
并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。
Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。
像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。
所以在很多人的概念里CPython就是Python,也就想当然的把GIL
归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL
二 GIL介绍
GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
假设有两个 Python 线程同时引用变量 a,那么双方就都会尝试操作该数据,很有可能造成引用计数的条件竞争,导致引用计数只增加 1(实际应增加 2)
这造成的后果是,当第一个线程结束时,会把引用计数减少 1,此时可能已经达到释放内存的条件(引用计数为 0,垃圾回收机制),当第 2 个线程再次视图访问 a 时,就无法找到有效的内存了。
所以,CPython 引进 GIL全局锁,可以最大程度上规避类似内存管理这样复杂的竞争风险问题。
首先确定一点:每次执行python程序,都会产生一个独立的进程。例如python test.py,python aaa.py,python bbb.py会产生3个不同的python进程
'''
#验证python test.py只会产生一个进程
#test.py内容
import os,time
print(os.getpid())
time.sleep(1000)
'''
python3 test.py
#在windows下查看进程
tasklist |findstr python
在一个python的进程内,不仅有test.py的主线程或者由该主线程开启的其他线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,所有线程都运行在这一个进程内,毫无疑问
1 所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(test.py的所有代码以及Cpython解释器的所有代码)
2 所有线程的任务,都需要将任务的代码当做参数传给解释器(的代码)去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器(的代码)。
上面这张图,就是 GIL 在 Python 程序的工作示例。
其中,Thread 1、2、3 轮流执行,每一个线程在开始执行时,都会锁住 GIL,以阻止别的线程执行;同样的,每一个线程执行完一段后,会释放 GIL,以允许别的线程开始利用资源。
了解:
其实,CPython 中还有另一个机制,叫做间隔式检查(check_interval)
意思是 CPython 解释器会去轮询检查线程 GIL 的锁住情况,每隔一段时间,Python 解释器就会强制当前线程去释放 GIL,这样别的线程才能有执行的机会。
# 三 GIL与多线程
多个线程先访问到解释器,即拿到执行权限(多个线程都去抢锁,抢到的先执行),然后将任务交给解释器去执行
解释器的代码是所有线程共享的
所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作
解决这种问题没有什么高明的方法,就是加锁处理,如下图的GIL,保证python解释器同一时间只能执行一个任务的代码
有了GIL的存在,同一时刻同一进程中只有一个线程被执行
听到这里,有的同学立马质问:进程可以利用多核,但是开销大,而python的多线程开销小,但却无法利用多核优势,也就是说python没用
别着急
要解决这个问题,我们需要在几个点上达成一致:
- cpu到底是用来做计算的,还是用来做I/O的?
- 多cpu,意味着可以有多个核并行完成计算,所以多核提升的是计算性能
- 每个cpu一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用处
结论:
对计算来说,cpu越多越好,但是对于I/O来说,再多的cpu也没用
了解:
有了 GIL,并不意味着 Python 程序员就不用去考虑线程安全了,因为即便 GIL 仅允许一个 Python 线程执行,但别忘了 Python 还有 check interval 这样的抢占机制。