写在前面:
先讲两个概念:
- 并发:多个任务交替执行,当任务一遇到阻塞或者时间片用完将其挂起,切换到任务二区执行。从宏观上来看也是多核任务“同时”执行。
- 并行:多任务同时执行,在多核心 CPU 时间,多个任务在多核 CPU 核心上同时执行。
Global Interpreter Lock 的前世今生
谈到多进程/多线程,数据安全是不可忽略的。而在 Python 的设计之初,计算机大多是单核的,多线程实际上是并发执行,GIL 的存在对效率影响不大。同时还方便保证了数据安全。
时至今日,我们的计算机处理器四核心、八核心、十六核心乃至更多。这时候因为 GIL 的存在限制了多线程在多核处理器上的运行效率。但是那为什么不直接去掉它?国外有个哥们曾写了一个取消 GIL 的分支,其中为了保证数据安全而不得不加 Luck,经测试下来执行效率并不理想。所以 GIL 一直存在到了现在。
Python 中的多线程是如何工作的?
首先呢,要先知道 GIL 是跟着进程走的,我们的代码是运行在一个进程上。
当你在这个进程中启动了多个线程时,这些线程任务都进入队列等待调度。当CPU的其中一个核心(我们暂时叫他核心1)调度到任一线程任务后(我们叫它任务1),加GIL锁然后执行任务。当这时,如果其他任一CPU核心调度到剩下的任一任务时,会先去检查 GIL,这是 GIL 可能存在两种状态:
- GIL 被锁:放弃执行重新放回队列中去
- GIL 已被释放:对 GIL 加锁,然后执行
所以说在同一个 GIL 锁的所有线程在任一时刻,永远只有一个在执行状态,GIL 限制了多线程的效率。
那多线程真的就一无是处了吗?
Python 多线程真的一无是处了吗?
在我们日常所写的代码可以分为计算密级型、IO 密集型。我们分开讨论:
- 计算密集型:当多线程中的任一任务时间片用完后会释放 GIL 锁,这时包括该任务在内的所有任务会进行 GIL 竞争(谁拿到谁执行),而所有任务都是拿到执行权后立即执行,不存在等待时间(网络、IO)。这样来回切换浪费资源与时间(线程调度也是需要资源与时间的)。我们把这种称为 ****
- 对 IO 密集型:当在任务执行过程中,如果遇到等待 IO 会直接让出其执行权,执行其他任务。这时候使用多线程对性能是有提升的。
所以,Python 多线程虽然很“鸡肋”,但是对 IO 密集型任务比较友好的。
简单理解下来,可以总结为两句话:
- 因为时间片用完而切换任务多线程不利,反而会降低效率
- 因阻塞切换的任务使用多线程可以有利
在我们的日常开发中可以酌情使用。如果真的为了更好的利用多核心 CPU 性能,还是比较推荐直接使用多进程来操作,这样 GIL 锁相互独立,就可以真正实现并行喽~。