为什么有人会说 Python 多线程是鸡肋?知乎上有人提出这样一个问题,在咱们常识中,多过程、多线程都是通过并发的形式充分利用硬件资源进步程序的运行效率,怎么在 Python 中反而成了鸡肋?
有同学可能晓得答案,因为 Python 中臭名远扬的 GIL,GIL 是什么?为什么会有 GIL?多线程真的是鸡肋吗?GIL 能够去掉吗?带着这些问题,咱们一起往下看,同时须要你有一点点急躁。
多线程是不是鸡肋,咱们先做个试验,试验非常简单,就是将数字 “1 亿 ” 递加,减到 0 程序就终止,这个工作如果咱们应用单线程来执行,实现工夫会是多少?应用多线程又会是多少?show me the code
# 工作
def decrement(n):
while n > 0:
n -= 1
单线程
import time
start = time.time()
decrement(100000000)
cost = time.time() - start
>>> 6.541690826416016
在我的 4 核 CPU 计算机中,单线程所花的工夫是 6.5 秒。可能有人会问,线程在哪里?其实任何程序运行时,默认都会有一个主线程在执行。(对于线程与过程这里不开展,我会独自开一篇文章)
多线程
import threading
start = time.time()
t1 = threading.Thread(target=decrement, args=[50000000])
t2 = threading.Thread(target=decrement, args=[50000000])
t1.start() # 启动线程,执行工作
t2.start() # 同上
t1.join() # 主线程阻塞,直到 t1 执行实现,主线程持续往后执行
t2.join() # 同上
cost = time.time() - start
>>>6.85541033744812
创立两个子线程 t1、t2,每个线程各执行 5 千万次减操作,等两个线程都执行完后,主线程终止程序运行。后果,两个线程以单干的形式执行是 6.8 秒,反而变慢了。按理来说,两个线程同时并行地运行在两个 CPU 之上,工夫应该减半才对,当初不减反增。
是什么起因导致多线程不快反慢的呢?
起因就在于 GIL,在 Cpython 解释器(Python 语言的支流解释器)中,有一把全局解释锁(Global Interpreter Lock),在解释器解释执行 Python 代码时,先要失去这把锁,意味着,任何时候只可能有一个线程在执行代码,其它线程要想取得 CPU 执行代码指令,就必须先取得这把锁,如果锁被其它线程占用了,那么该线程就只能期待,直到占有该锁的线程开释锁才有执行代码指令的可能。
因而,这也就是为什么两个线程一起执行反而更加慢的起因,因为同一时刻,只有一个线程在运行,其它线程只能期待,即便是多核 CPU,也没方法让多个线程「并行」地同时执行代码,只能是交替执行,因为多线程波及到上线文切换、锁机制解决(获取锁,开释锁等),所以,多线程执行不快反慢。
什么时候 GIL 被开释呢?
当一个线程遇到 I/O 工作时,将开释 GIL。计算密集型(CPU-bound)线程执行 100 次解释器的计步(ticks)时(计步可粗略看作 Python 虚拟机的指令),也会开释 GIL。能够通过 sys.setcheckinterval()
设置计步长度,sys.getcheckinterval()
查看计步长度。相比单线程,这些多是多线程带来的额定开销
CPython 解释器为什么要这样设计?
多线程是为了适应古代计算机硬件高速倒退充分利用多核处理器的产物,通过多线程使得 CPU 资源能够被高效利用起来,Python 诞生于 1991 年,那时候硬件配置远没有明天这样奢华,当初一台一般服务器 32 核 64G 内存都不是什么司空见惯的事,然而多线程有个问题,怎么解决共享数据的同步、一致性问题,因为,对于多个线程访问共享数据时,可能有两个线程同时批改一个数据状况,如果没有适合的机制保证数据的一致性,那么程序最终导致异样,所以,Python 之父就搞了个全局的线程锁,不论你数据有没有同步问题,反正一刀切,上个全局锁,保障数据安全。这也就是多线程鸡肋的起因,因为它没有细粒度的控制数据的平安,而是用一种简略粗犷的形式来解决。
这种解决办法放在 90 年代,其实是没什么问题的,毕竟,那时候的硬件配置还很简陋,单核 CPU 还是支流,多线程的利用场景也不多,大部分时候还是以单线程的形式运行,单线程不要波及线程的上下文切换,效率反而比多线程更高(在多核环境下,不实用此规定)。所以,采纳 GIL 的形式来保证数据的一致性和平安,未必不可取,至多在过后是一种老本很低的实现形式。
那么把 GIL 去掉可行吗?
还真有人这么干多,然而后果令人悲观,在 1999 年 Greg Stein 和 Mark Hammond 两位哥们就创立了一个去掉 GIL 的 Python 分支,在所有可变数据结构上把 GIL 替换为更为细粒度的锁。然而,做过了基准测试之后,去掉 GIL 的 Python 在单线程条件下执行效率将近慢了 2 倍。
Python 之父示意:基于以上的思考,去掉 GIL 没有太大的价值而不用花太多精力。
小结
CPython 解释器提供了 GIL(全局解释器锁)保障线程数据同步,那么有了 GIL,咱们还须要线程同步吗?多线程在 IO 密集型工作中,体现又怎么呢?欢送大家留言, 看到这里点个赞再走吧~ 感激浏览。