一句话定义:进程(Process)是操作系统分配资源的最小单位,线程(Thread)是操作系统调度 CPU 的最小单位。一个进程里可以容纳多个线程,它们共享这个进程的内存空间。
打个比方:进程像一栋独立厂房,有自己的地皮和水电表,也就是独立的内存地址空间;线程则像厂房里的工人,共用同一间仓库。好处是拿东西方便,坏处是一个工人把仓库点着了,整栋厂房都得停工。厂房与厂房之间则互不影响。
| 对比项 | 进程 | 线程 |
|---|---|---|
| 内存 | 各自独立 | 共享所属进程的内存 |
| 创建 / 切换开销 | 大 | 小 |
| 通信方式 | 需要序列化:管道、队列、共享内存 | 直接读写同一变量,但要自己加锁 |
| 隔离性 | 强,一个崩了不牵连别人 | 弱,一个崩了整组陪葬 |
| 典型适用 | CPU 密集型、需要稳定隔离 | I/O 密集型、需要频繁共享数据 |
GIL 到底限制了谁:CPython 解释器里有一把全局锁,叫全局解释器锁(Global Interpreter Lock,GIL),它保证同一时刻只有一个线程在执行 Python 字节码。因此在一台多核机器上开多个线程做纯 Python 的循环计算,总耗时和单线程差不多,甚至更慢,因为线程切换本身有开销。这也是很多人说“Python 多线程是假的”的由来。
但这个说法只对了一半。GIL 不限制以下两种情况:
- 等待型任务:读文件、发网络请求、查数据库时,线程会主动释放 GIL,别的线程照跑不误。所以爬虫、API 调用这类 I/O 密集型任务,多线程是有效的。
- 会释放 GIL 的 C 扩展:NumPy 的矩阵运算、PyTorch 的底层算子,在 C/C++ 里计算期间通常不持有 GIL,多线程依然能并行。
所以 Python 的并发方案总得分两种情况讨论:I/O 密集型用多线程或协程(asyncio),写起来简单、开销小;CPU 密集型用多进程(multiprocessing),每个进程都有独立的解释器和自己的 GIL,能真正吃满多核,代价是进程间传数据要序列化、启动慢、内存占用成倍增加。
给从业者的操作建议:先判断瓶颈是“在等”还是“在算”。在等,就上线程或协程;在算,就上多进程。还有第三条路常被忽略:把热点循环换成向量化写法,交给 NumPy / PyTorch 这类底层会释放 GIL 的库,往往比换并发模型收益更大。另外线程和进程都不是越多越好,上下文切换和锁竞争会反过来吃掉收益。
一点补充:GIL 是 CPython 这个具体实现的产物,并非 Python 语言规范的规定,其他实现未必如此,具体行为以官方文档为准。真正跨语言的通用结论是那句老话:进程管隔离,线程管共享;要隔离就付通信成本,要共享就承担风险。
