1. 并发调度基础:进程与线程概念、GIL锁对Python并发的影响、并发与并行的区别
聊并发调度,得先从最基础的两个概念说起——进程和线程。我刚开始接触这块时,也经常搞混。说白了,进程是资源分配的最小单位,线程是CPU调度的最小单位。嗯,记住这句话,后面就好理解了。
1.1 进程与线程:到底差在哪?
进程就像一家独立的公司。有自己的办公场地(内存空间)、自己的财务(文件句柄)、自己的员工(线程)。每个进程之间互不干扰,一家公司倒闭了,不影响隔壁公司继续营业。
线程呢?就是公司里的员工。同一家公司的员工共享办公场地、共享打印机、共享茶水间。但问题也来了——如果两个员工同时要用打印机,就得排队,不然会乱套。这就是线程同步要解决的问题。
我在项目中遇到过这样一个场景:一个Web服务要处理大量用户请求。如果每个请求都开一个新进程,内存开销巨大,响应也慢。后来改成线程池,一个进程里开几十个线程,共享内存,切换成本低,吞吐量直接翻倍。
核心区别总结:
- 资源开销:进程间切换成本高,线程间切换成本低
- 内存隔离:进程有独立地址空间,线程共享进程内存
- 通信方式:进程间通信(IPC)复杂,线程间通信简单(共享变量)
- 健壮性:一个进程崩溃不影响其他进程;一个线程崩溃可能拖垮整个进程
1.2 GIL锁:Python并发的心头痛
说到Python并发,绕不开GIL(全局解释器锁)。你想想看,Python这么火的语言,为什么在CPU密集型任务上表现那么差?答案就是GIL。
GIL是什么?简单说,CPython解释器里有一把大锁。这把锁规定:同一时刻,只有一个线程能执行Python字节码。不管你开多少个线程,真正干活儿的只有一个。
为什么会这样?历史原因。Python的内存管理不是线程安全的。早期设计者为了省事,直接加了一把全局锁。嗯,这个设计在当时看来没问题,但现在成了性能瓶颈。
避坑指南:
我曾经在一个数据处理项目里,用多线程去加速计算。开了16个线程,结果CPU利用率只有12%左右。排查了半天,才发现是GIL在作祟。后来改用多进程,每个进程独立一个GIL,CPU利用率直接飙到95%以上。
GIL的影响分两种情况:
| 任务类型 | GIL影响 | 推荐方案 |
|---|---|---|
| CPU密集型(计算、加密、压缩) | 严重,多线程几乎无加速 | 多进程(multiprocessing) |
| I/O密集型(网络请求、文件读写) | 较小,I/O等待时释放GIL | 多线程(threading)或异步(asyncio) |
说白了,如果你的程序大部分时间在等I/O,多线程没问题。但如果是在做大量计算,多线程反而可能比单线程还慢——因为线程切换也有开销。
1.3 并发与并行:一字之差,天壤之别
这两个词经常被混用,但本质完全不同。我习惯用一个比喻来解释:
- 并发:一个人同时处理多件事。比如你一边写代码一边听音乐,看起来是同时在做,实际上大脑在快速切换。
- 并行:多个人各自处理一件事。比如你和同事同时写不同的代码模块,互不干扰。
在计算机里,单核CPU只能实现并发(时间片轮转),多核CPU才能实现真正的并行。你想想看,如果你的电脑是4核8线程,理论上最多可以并行执行8个任务。
注意:
并发不一定要并行。一个单核CPU照样可以跑几百个并发任务,只是看起来像同时执行而已。并行一定需要多核硬件支持。很多新手把并发等同于多线程,其实单线程异步也是并发的一种形式。
我在做调度系统时,就踩过这个坑。一开始设计成多线程并行处理任务,结果部署到2核的服务器上,性能还不如单线程。后来改成异步I/O + 多进程混合模式,才真正把硬件性能榨干。
1.4 知识体系总览
下面这张图,把本章的核心逻辑串起来了。我建议你多看几遍,理清关系。
这张图把本章的三个核心知识点串起来了。从左到右分别是进程与线程、GIL锁、并发与并行。你看,它们不是孤立的——GIL锁直接影响了你该选进程还是线程,而并发与并行的区别决定了你的硬件资源能不能被充分利用。
我的建议:
刚开始学并发调度,别急着写代码。先把这三个概念理清楚。我见过太多人一上来就开几十个线程,结果性能反而下降,最后发现连GIL是什么都不知道。基础打牢了,后面学协程、锁、信号量这些才不费劲。
好了,这一章就到这里。记住:进程是公司,线程是员工,GIL是公司里那把唯一的钥匙,并发是聪明地安排工作,并行是真正地同时干活。把这些想明白了,后面的内容就好理解了。