你写Python的时候,是不是经常听到“Python多线程是假的”这种话。听起来有点扎心对吧。
我刚开始学的时候也不信。自己写了个爬虫,开了十几个线程去抓数据,结果CPU利用率低得可怜。机器风扇都不转。
后来我才想明白一个道理。Python的多线程更像一个茶餐厅里只有一个服务员。
你把客人当成任务,服务员就是那个唯一的GIL锁。客人都排队等着点餐,服务员一次只能招呼一个人。其他客人哪怕再饿,也只能干瞪眼等着。
这就像Python的全局解释器锁,英文叫GIL。它让一个进程里同时只能有一个线程在跑Python字节码。你开十个线程,本质上还是串行执行。一个人干了十个人的活,只是切换得很快。
真实的多线程是什么样子呢。比如你写C++的程序,开十个线程。那就是十个服务员同时上菜。十个客人同时吃饭。CPU有十颗核心就能同时跑十根线程。
因为GIL的存在,Python的threading模块对计算密集型的任务几乎没用。你算个圆周率,开一百个线程也不会比单线程快。
但有个特例。如果你的任务是在等输入输出,比如下载网页、读写文件、查询数据库。这时候线程就有用了。
因为线程在等网络响应的时候,它不会占着服务员不放。它会主动让出GIL锁。这时候其他线程就能跑。一个线程在等数据,另一个线程就在干活。这就叫I/O密集型任务。
所以你写爬虫的时候,开多线程确实能加快速度。不是CPU变快了,是等待的时间被叠加起来了。一个线程等的时候,另一个线程在请求。
要是你真的需要并行计算,Python有别的办法。用multiprocessing模块开多个进程。每个进程有自己的GIL,互不干扰。就像开了好几家茶餐厅,各做各的生意。
进程的缺点是启动慢,通讯也麻烦。但是为了真并行,这点代价值得掏。
还有一种方式是换解释器。像Jython就没有GIL。但大部分情况下你还是用CPython,那就得接受这个现实。
在团队里经常有人争论这个问题。有人觉得GIL是Python的硬伤,有人觉得日常开发根本遇不到瓶颈。你得想清楚自己的场景。
如果你只是写个脚本处理日志,或者接几个外部接口。多线程完全够用。别被那些“假多线程”的说法吓住了。
但如果你要搞图像处理、科学计算、深度学习这种需要大量数学运算的。老老实实去用C扩展或者写numPy。或者直接用多进程。
记住那个茶餐厅服务员的类比。你就能明白为什么有人抱怨,有人不在乎。不是Python不行,是你没选对工具。