GIL 产生的背景:
在 Cpython 解释内部运行多个线程的时候, 每个线程都需要解释器内部申请相应的全局资源, 由于 C 语言本身比较底层造成Cpython 在管理所有全局资源的时候并不能应对所有线程同时的资源请求, 从而为了防止资源竞争而发生错误, 对所有线程申请全局资源增加了限制----全局解释器锁
言外之意就是全局解释器就是为了锁定整个解释器内部的全局资源, 每个线程想要运行首先获取 GIL, 而 GIl 本身又是一把互斥锁, 造成所有线程只能一个一个 并发—交替的执行
因此, GIL 全局解释器锁就是解释器内部的一把锁, 确切一点说是 Cpython 解释器内部的一把锁, 所有要注意区分.这和我们在 Python 代码中 使用 Lock 不是一个层面的概念
Guido 的声明:Python 之父*在观点的最后说明: the language doesn’t require the GIL – it’s only the Cpython virtual machice that has historically been unable to shed it.
GIL 什么时候释放
- 在当前线程执行超时后会自动释放
- 在当前线程执行阻塞操作时会自动释放
- 当前执行完成时
GIL 面试题
问题: 描述 Python GIL 的 概念, 以及他对 python 多线程的影响? 编写一个多线程抓取网页的程序, 并阐述多线程抓取程序是否可比单线程性能有提升 ,并解释原因.
首先 python 和 GIL 一分钱的关系都没, 仅仅是由于历史原因在 Cpython 解释器, 难以移除 GIL.
- GIL : 全局解释器锁. 每个线程在执行的国会曾都需要先获取 GIL , 保证同一时刻只有一个线程可以执行代码.
- 线程释放 GIL 锁的情况: 1. 在 IO 操作等可能会引起阻塞的 system call 之前, 可以暂时释放 GIL , 但在执行完毕之后, 必须重新获取 GIL. 2. python 3.x 使用计时器(执行时间达到阙值后, 当前线程释放GIL) , 或者二 Python 2.x, tickets 计数达到 100
- Python 使用多进程是可以利用多核的 CPU 资源的
- 多线程爬取比单线程性能有提升, 因为遇到 IO 阻塞会自动释放 GIL 锁
严重问题
既然 Cpython 解释存在 GIL 是否意味着每个线程在全局就不用加 Lock 互斥锁了呢?
这是一个很严重的错误想法, 为什么用户操作全局变量还需要加 Lock, 因为 GIL 的释放实际我们无法控制, 操作可能还没并没有完成, 而不像 Lock 那样我们用完才释放(操作完整)
Talk is cheap, show me the code
import threadind
g_number = 0
def func1():
global g_number
for i in range(1000000):
g_number += 1
def func2():
global g_number
for i in range(1000000):
g_number += 1
def main():
# 创建一个子线程 让他修改全局变量
thd1 = threading.Thread(target=func1)
thd2 = threading.Thread(target=func2)
thd1.start()
thd2.start()
# 2 主线程等待子线程退出 再去查看全局变量的结果
thd1.join()
thd2.join()
print("全局变量的值是%d" % g_number)
if __name__ == "__main__":
main()
"""
线程1 取出全局变量 g_number == 0
假如一直加到20000,但是还没有来得及将20000写入g_number中,
就超时了自动释放GIL 而线程2取出 g_number==19999
加到39999由于超时自动释放GIL
此时线程2获取到GIL继续着未完成的事
因此这个多线程案例没有加互斥锁的情况最终结果是 120w 左右(如果加上互斥锁的话就是2000000了)
"""
# 控制台输出
全局变量的值是 12096721