什么是GIL (Global Interpreter Lock)?
行,我说下GIL这个事儿,最近加班太多,脑子已经有点迷糊了...昨晚十点多,还在公司楼下拿着手机刷B站,突然群里有人问,“为啥Python多线程就是不快啊,是不是有啥隐藏开关啊?”我当时正啃着一根玉米,差点呛住,回了句“你是不是没听说过GIL?”
其实这个GIL(Global Interpreter Lock)啊,说白了,就是...呃,全局解释器锁嘛,字面意思——Python解释器里头有个大锁,所有线程都得抢这个锁才能跑。感觉就像食堂有一把唯一的饭勺,大家排队盛饭,谁拿着勺,谁吃饭,别人都只能等着。
我记得很早之前,用threading写个小demo,想着能不能CPU满载一下,结果根本没用。你就比如这样:
import threading
defadd():
x = 0
for i in range(10000000):
x += 1
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=add)
t1.start()
t2.start()
t1.join()
t2.join()
你表面上看,两个线程都在累加,应该是“并行”地加才对吧?可你去看任务管理器,CPU根本没起来——其实就是因为GIL。GIL限制了同一时刻只有一个线程在执行Python字节码,哪怕你开一百个线程,本质上还是一个在跑,其他都在等饭勺。
这里有同学要问了,那为啥要搞这么个锁啊,Python开发组是不是吃饱了撑的?其实,这个锅得扔给CPython(Python主流实现)的设计。主要是为了保证对象内存管理的安全性。你想,Python有垃圾回收,有引用计数,要是多线程一起改引用计数,很容易就乱套了。
所以就有了这个GIL,大家都排队来操作,虽然单线程安全了,但多线程效率就......你懂的,真的鸡肋。
但你要说多线程一无是处,其实也不至于。有些IO密集型场景,比如爬虫、读写文件、网络请求,这种时候线程经常在等IO,CPU其实闲着的,这会GIL会自动切出去,让其他线程有机会运行。所以你用多线程写爬虫,还是挺香的。但一旦是纯粹的计算密集型任务,比如图片处理、科学计算,GIL就是噩梦,完全拉胯。
说个有意思的,之前我们组的小李写了个多线程矩阵运算,结果还没单线程快,他一度以为自己代码写炸了,debug了两小时,最后发现是GIL在捣鬼。后来换成multiprocessing模块,把任务丢到多个进程里去跑,才解决——因为每个进程都有自己独立的Python解释器和GIL,互不干扰,终于CPU用起来了。
再补一句哈,其实还有PyPy、Jython这些Python实现没GIL,但生态没CPython那么全,所以主流项目还是离不开GIL这坎。现在也有不少人呼吁Python能彻底干掉GIL,Py3.12好像有点新动作,但还没普及。唉,说多了都是泪。
总之,你要是用Python干CPU密集的活儿,记得别想着多线程,直接上多进程,或者搞点C扩展,别跟自己过不去。至于IO密集,多线程还是能发挥点余热的。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领