Python中的GIL问题

本文探讨了Python中的全局解释器锁(GIL)现象,解释了它为何会导致多线程性能瓶颈,并提出了几种可能的解决策略,旨在帮助开发者优化Python多线程程序。
摘要由CSDN通过智能技术生成


查看原文:http://www.wyblog.cn/2017/02/18/python%e4%b8%ad%e7%9a%84gil%e9%97%ae%e9%a2%98/

定义

GIL全称是Global Interpreter Lock,首先看看官方定义:
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
定义说,GIL是多线程间的一把互斥锁,并且是一把全局锁,它保证了Cpython在内存管理上面是线程安全的。 这里要注意一点的是,官方定义开头就给了限制范围,是在CPython这个解释器下。我们知道,python存在各种各样的解释器,也就是说,在某些解释器下,其实并不存在GIL这个东西,经过查阅资料,像JPython中就不存在GIL。

背景

为了发挥多核CPU性能,程序多采用多线程/多进程方式设计,这里仅关注多线程模式。多线程模式必然涉及到线程之间的通信或者同步问题,最简单粗暴的解决方式就是加锁,GIL因此而诞生。事实上GIL在设计上存在一定缺陷,在多核心CPU上,GIL的表现特别差,但是因为GIL诞生的很早,所以大量模块中都应用了它,所以当后来开发者发现GIL效率存在严重问题时,已经很难根除这个家伙了。所以,GIL算是一个Python的历史遗留问题。

为什么会出问题?

Python为了方便,并没有做一套自己的线程机制,而是直接调用的系统线程,也就是说,python的线程实际上就是操作系统上的POSIX thread。然而对于任务调度,python中每个任务在执行固定长度的代码片段(100条字节码)后,便会引起操作系统进行一次任务调度,最终哪个任务拿到CPU使用权,取决于其优先级。   GIL的bug,主要问题就出现在多核心CPU Bound(计算密集型)的任务上。下面这幅图简单地说明了情况: 当线程一释放了GIL,引起操作系统进行任务调度期间可能立即又获取到了GIL锁,导致第二个核心上的线程二激活后根本没机会拿到使用权限,这就相当于CPU一直是单核心单线程运作。 总结下就是,对于多核心CPU,操作系统是同时可以启动多个线程分别在不同的核心上运行,但是由于GIL是关于线程的全局锁,就可能导致某个任务一直不停地acquire到GIL,使得其他核心上的线程仅仅是不停地在retry GIL,造成了堵塞的现象。 截取一张别人的实验数据: 上下两行分别是CPU两个核心的执行情况,绿格子之间暗红色格子,就是因为没有获取到GIL锁一直在不断retry的过程,这相当于是阻塞了CPU的一个核心。 将其放大后更明显: 以上讨论的是CPU Bound型任务,对于IO Bound(IO密集型)任务,GIL缺陷就不那么明显,当线程去执行IO时,GIL就被释放掉了,其他线程自然能够顺利进行。

如何解决?

方案一:GIL既然是针对线程的锁,那我们如果直接使用Python进行多进程编程,就可以绕过GIL了,Python中有对应的模块,名字叫multiprocesssing。但是,对于进程来说,进程间的通信又需要我们手动实现,大大增加了编程的难度及复杂性。 方案二:从Python 3.2开始,实现了新的GIL,如下图: 新的机制容易理解,多了一个用于通信的gil_drop_request全局变量,根据名称可知,其实就是个flag,当有线程二acquire GIL直到TIMEOUT后,就会将其置为1并进入等待状态,正在运行的线程一收到信号量后,就会给一个signal回去并也进入等待状态,线程二收到signal就说明GIL已被释放,于是成功拿到GIL并运行,同时也不忘回送一个ack信号给线程一,告诉他你可以挂起了,整个过程类似于TCP的握手过程。这种机制存在缺点,对IO Bound型任务,难道每次获取GIL时都得等到TIMEOUT?那浪费的时间就太多了。

参考资料

http://www.dabeaz.com/GIL/ http://cenalulu.github.io/python/gil-in-python/


查看原文:http://www.wyblog.cn/2017/02/18/python%e4%b8%ad%e7%9a%84gil%e9%97%ae%e9%a2%98/
PythonGIL(全局解释器锁)是一种机制,它确保在任何给定时间只有一个线程在解释器执行字节码。这意味着在多线程的情况下,Python的多线程并不能真正实现并行执行,而只是并发执行。 GIL的存在是为了保护Python解释器内部数据结构的一致性,因为这些数据结构在多线程环境下可能会出现竞争条件。然而,这也导致了Python在处理计算密集型任务时的性能问题,因为只有一个线程可以执行字节码。 虽然GIL对于IO密集型任务并不是一个问题,因为线程在等待IO操作完成时会释放GIL,但对于计算密集型任务,GIL会成为性能瓶颈。 要解决GIL的限制,有几种方法可以尝试: 1. 使用多进程而不是多线程:Python的multiprocessing模块提供了一种在多个进程执行任务的方式,每个进程都有自己的解释器和GIL。这样可以实现真正的并行执行。你可以使用multiprocessing模块来将计算密集型任务分配给多个进程执行。 2. 使用其他解释器:除了CPython,还有其他的Python解释器,如Jython、IronPython和PyPy。这些解释器没有GIL的限制,因此可以实现真正的并行执行。但需要注意的是,这些解释器可能不支持所有的Python库和功能。 3. 使用C扩展:对于计算密集型任务,可以使用C扩展来绕过GIL。通过将计算部分的代码编写为C扩展,可以在不受GIL限制的情况下执行计算。 下面是一个使用多进程的示例代码,演示了如何绕过GIL实现并行执行: ```python from multiprocessing import Pool def calculate_square(n): return n * n if __name__ == '__main__': numbers = [1, 2, 3, 4, 5] pool = Pool() result = pool.map(calculate_square, numbers) print(result) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值