理解GIL,Python多线程的真相
好多Python老手写了好几年代码,多线程用的飞起。一问GIL是什么,支支吾吾答不上来。这就像开车不按后视镜,只管往前冲。迟早要出事的。
GIL全名叫Global Interpreter Lock,全局解释器锁。这玩意儿是CPython解释器里的一个机制。它规定同一时刻,只能有一个线程在执行Python字节码。别的线程想干活?等着吧。
你可能觉得这不合理。多核CPU的时代,一个锁把性能限制得死死的。但这是设计缺陷吗?不是。Python诞生那会儿,单核CPU是主流。GIL让内存管理变得简单,不用考虑多线程争抢资源的问题。很多底层C扩展库也是基于这个假设写的。
说白了,GIL是Python为了安全搞的“单行道”。好处是普通开发者不容易写出内存泄漏的并发代码。坏处是计算密集型的多线程程序,在多核机器上跑出来跟单线程一样慢。
你写个爬虫,开20个线程抓网页。工作线程大部分时间在等网络响应,CPU空闲着。GIL在这时候基本不作妖。网络IO快的很,立马释放锁,其他线程可以接力。这种场景多线程没问题。
但你要是用Python写图像处理,或者跑一个死循环算圆周率。那完蛋了。每个线程只能拿到很少的CPU时间片,切换来切换去,效率甚至不如单线程。你看着CPU使用率只有100%,却开了8个线程,怀疑人生。
有人问,能不能去掉GIL?能。但代价太大。去掉GIL,Python的垃圾回收全部要重写。所有基于C的第三方库都可能崩。现在的Python生态太庞大了,想动这个根基,基本不可能。
那怎么办?有路子。CPU密集型的任务,用multiprocessing模块。多进程,每个进程有自己的GIL,互不干扰。缺点是你得自己处理进程间通信。数据共享比多线程麻烦。
IO密集型的任务,直接上多线程。或者用Python3.4以后加入的asyncio。异步编程,单线程里用事件循环处理IO请求。效率比多线程还高,还不受GIL影响。
知道GIL的存在,不是为了抱怨它。是为了在选择方案时,心里有数。你用框架,比如Django,它处理请求默认是多线程的。大部分业务逻辑都是查数据库,IO占了主要时间。GIL不是瓶颈。你要写个实时视频处理服务,还用Django那套多线程等着哭吧。
还有个小细节。Python的GIL在设计上有一些优化。比如线程在等待IO时会主动释放GIL。Python3.2之后也引入了一些改进,让GIL在切换时更公平,不会饿死某个线程。但还是那句话,计算密集型的多线程,性能瓶颈就在那个锁上。
我见过一些项目,面试时候说用了多线程提升性能。结果代码一看,是计算型任务,跑在服务器上。那多线程纯粹为了面试写的,实际没任何提升。这种人就是只踩油门不看路,迟早撞墙。
总结一句实际的。搞Python的人,可以不写多线程。但提到GIL,必须能说清楚它是什么,影响在哪,怎么绕过。这是基本功。就像老司机挂挡前会扫一眼后视镜。不是每天都要用。但遇到变道超车,你少看一眼都可能出事。