python中 GIL的有关问题,以及面试中可能会被问到的问题

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 什么时候释放

  1. 在当前线程执行超时后会自动释放
  2. 在当前线程执行阻塞操作时会自动释放
  3. 当前执行完成时

GIL 面试题

问题: 描述 Python GIL 的 概念, 以及他对 python 多线程的影响? 编写一个多线程抓取网页的程序, 并阐述多线程抓取程序是否可比单线程性能有提升 ,并解释原因.

首先 python 和 GIL 一分钱的关系都没, 仅仅是由于历史原因在 Cpython 解释器, 难以移除 GIL.

  1. GIL : 全局解释器锁. 每个线程在执行的国会曾都需要先获取 GIL , 保证同一时刻只有一个线程可以执行代码.
  2. 线程释放 GIL 锁的情况: 1. 在 IO 操作等可能会引起阻塞的 system call 之前, 可以暂时释放 GIL , 但在执行完毕之后, 必须重新获取 GIL. 2. python 3.x 使用计时器(执行时间达到阙值后, 当前线程释放GIL) , 或者二 Python 2.x, tickets 计数达到 100
  3. Python 使用多进程是可以利用多核的 CPU 资源的
  4. 多线程爬取比单线程性能有提升, 因为遇到 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
发布了56 篇原创文章 · 获赞 17 · 访问量 2148

猜你喜欢

转载自blog.csdn.net/LanlanDeming/article/details/103649090
今日推荐