很多初学 Python 并发的小伙伴都会遇到同一个诡异问题:
两段几乎一模一样的代码,分别创建两个任务并行执行:
1. 使用threading.Thread启动两个计算函数,运行结果永远先执行完 func1,再执行 func2,明显串行排队;
2. 使用multiprocessing.Process启动同样两个函数,两个方法同时运行,耗时直接减半。
明明都是同时启动,执行顺序却天差地别,不少人疑惑:难道线程不支持并发?进程天生就能并行?
今天结合可运行实测代码 + 底层原理,一次性把这个高频坑讲明白,看完彻底分清线程、进程适用场景。
一、复现问题:两段对比代码
代码 1:threading 双线程(纯计算,串行执行)
import threading
import time
def func1():
# CPU密集运算
total = sum(i for i in range(10**7))
print("func1 执行完成")
def func2():
total = sum(i for i in range(10**7))
print("func2 执行完成")
if __name__ == "__main__":
start = time.time()
t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"总耗时:{time.time() - start:.2f}s")
运行输出固定顺序:
func1 执行完成
func2 执行完成
总耗时≈两次计算累加(4 秒左右)
代码 2:multiprocessing 双进程(纯计算,并行执行)
import multiprocessing
import time
def func1():
total = sum(i for i in range(10**7))
print("func1 执行完成")
def func2():
total = sum(i for i in range(10**7))
print("func2 执行完成")
if __name__ == "__main__":
start = time.time()
p1 = multiprocessing.Process(target=func1)
p2 = multiprocessing.Process(target=func2)
p1.start()
p2.start()
p1.join()
p2.join()
print(f"总耗时:{time.time() - start:.2f}s")
运行输出随机顺序,几乎同时结束:
func1 执行完成
func2 执行完成
总耗时≈单次计算(2 秒左右)
现象总结:同样两个计算任务,线程排队串行,进程同步并行。
二、核心根源:CPython GIL 全局解释器锁
1. 什么是 GIL?
GIL 全称全局解释器锁,是 CPython 自带的一把互斥锁,硬性规定:
同一个进程内,同一时刻只能有 1 个线程执行 Python 字节码。
通俗比喻:
一个办公室(进程)里,只有 1 张通行证(GIL 锁),所有员工(线程)轮流抢通行证。拿到通行证才能干活,没拿到只能原地等待。
2. 为什么纯计算线程会固定先跑完 func1?
纯 CPU 运算代码全程持续占用 GIL,只有两种场景会释放锁:
1. 线程执行满固定时间片(几十毫秒);
2. 线程函数执行完毕。
代码执行流程拆解:
1. t1.start() 启动线程 1,优先抢到 GIL,开始疯狂循环计算;
2. t2.start() 启动线程 2,没有通行证,持续阻塞等待;
3. 海量循环计算会一次性耗尽任务,还没到系统切换时间片,func1 直接运行结束;
4. func1 结束后释放 GIL,func2 才能拿到锁,开始计算。
肉眼效果:固定先执行 func1,再执行 func2,完全串行。
补充:带 IO/sleep 的线程不会串行
如果函数里加入time.sleep()、网络请求、文件读写等 IO 操作,线程会主动释放 GIL,此时两个线程真正并发,结束顺序随机:
import threading, time
def func1():
time.sleep(2)
print("func1 完成")
def func2():
time.sleep(2)
print("func2 完成")
t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)
t1.start()
t2.start()
t1.join()
t2.join()
运行后两个函数几乎同时打印,不存在固定先后顺序。
👉 结论:GIL 只限制 CPU 密集运算,IO 阻塞会自动释放锁,线程可并发。
三、多进程为什么能同时执行两个方法?
进程是操作系统资源分配最小单位,和线程有本质区别:
1. 每创建一个Process,都会生成独立全新 Python 解释器、独立内存空间、独立 GIL 锁;
2. 操作系统调度器可把不同进程分配到不同 CPU 物理核心,真正并行运算;
3. 进程之间互不干扰,不存在抢同一把 GIL 锁的问题。
流程拆解:
1. p1、p2 是两个完全独立操作系统进程;
2. 双核 CPU 下,系统分别给两个进程分配核心;
3. func1、func2 同时在两个核心上计算,同步完成,耗时减半。
特殊情况:单核 CPU 跑多进程
如果电脑只有单核,进程依然会交替切换执行,但不会出现线程那种固定排队现象,只是无法实现多核加速。