很多人学 Python 时,都会遇到一个迷惑: 明明我的电脑是 8 核 16 线程的猛兽,为什么 Python 跑起来还是跟蜗牛一样?
答案就是三个字母 ------ GIL(全局解释器锁) 。
这个"锁"就像一个狭窄的单行道,哪怕有再多的车(线程),一次也只能过一辆。于是你会发现,写了再多的多线程代码,CPU 占用率还是死活上不去。
那么问题来了:想让 Python 代码跑得飞快,能真正用上多核 CPU,我们该怎么办? ------答案就是:用多进程(multiprocessing) 。
这篇文章,我会带你从零开始搞懂 Python 的多进程:从创建子进程,到进程间通信,再到进程池优化。代码都有,讲解也够接地气。看完你会发现,多进程其实并不难,而且真的是性能杀手锏。

一、为什么非得用多进程?
先举个真实的例子:
有个朋友写了个爬虫,用多线程抓数据,本来以为能飞快采集。结果 CPU 占用率才 20%,剩下的 80% 跟喝咖啡去了。原因就是 Python 的线程被 GIL 卡住了,线程不是同时跑,而是轮流跑。
而多进程完全不同。每个进程就像一个独立的工厂,操作系统调度它们"并行"运行。你开 4 个进程,理论上就是 4 倍速度。尤其在做 CPU 密集型任务(计算、图像处理、数据分析) 时,多进程的效果肉眼可见。
所以一句话:
- I/O 密集型 → 多线程(比如爬虫、网络请求)
- CPU 密集型 → 多进程(比如矩阵计算、加密解密)
搞清楚这个原则,你就不会乱用。
二、最简单的多进程示例
说一千道一万,不如上代码。
下面这个例子,你可以直接跑:
scss
import multiprocessing as mp
import time
def worker(name):
print(f"{name} 进程启动")
time.sleep(2)
print(f"{name} 进程结束")
if __name__ =='__main__':
p1 = mp.Process(target=worker, args=("进程1",))
p2 = mp.Process(target=worker, args=("进程2",))
p1.start()
p2.start()
p1.join()
p2.join()
print("主进程结束")
运行结果你会发现:两个子进程几乎同时启动,而不是一个等另一个结束才开始。
这就是"并行"的魅力。
三、除了函数,还能继承类来写
有些场景下,我们可能想要把进程封装得更清晰,可以直接继承 Process
来写:
python
import multiprocessing as mp
import time
class MyProcess(mp.Process):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print(f"{self.name} 执行中")
time.sleep(1)
print(f"{self.name} 退出")
if __name__ =='__main__':
p = MyProcess("自定义进程")
p.start()
p.join()
这种写法更 OOP,一看就是"我要定义一个特殊的进程",代码也更容易扩展。
四、进程之间怎么交流?
你可能会问:进程之间内存不共享,那它们怎么交换数据?
答案就是三种方式:队列(Queue)、管道(Pipe)、共享内存(Value/Array) 。
1. 队列(推荐)
就像工厂里的传送带,一个进程负责放东西,另一个负责拿:
python
def producer(q):
for i in range(5):
q.put(f"消息 {i}")
print(f"生产: 消息 {i}")
def consumer(q):
while True:
item = q.get()
if item is None: break # 终止信号
print(f"消费: {item}")
if __name__ =='__main__':
queue = mp.Queue()
p1 = mp.Process(target=producer, args=(queue,))
p2 = mp.Process(target=consumer, args=(queue,))
p1.start()
p2.start()
p1.join()
queue.put(None) # 发送终止信号
p2.join()
这种方式最实用,特别适合生产者-消费者模型。
2. 管道(Pipe)
如果只是"一对一"通信,管道就够了:
scss
def child_process(conn):
conn.send("子进程消息")
print("子进程收到:", conn.recv())
conn.close()
if __name__ =='__main__':
parent_conn, child_conn = mp.Pipe()
p = mp.Process(target=child_process, args=(child_conn,))
p.start()
print("父进程收到:", parent_conn.recv())
parent_conn.send("父进程消息")
p.join()
3. 共享内存(Value / Array)
这种方式更高效,但只适合存放简单数据:
ini
def modify(n, arr):
n.value = 3.14
for i in range(len(arr)):
arr[i] = arr[i] * 2
if __name__ =='__main__':
num = mp.Value('d', 0.0) # 'd' 表示双精度浮点
arr = mp.Array('i', range(5)) # 'i' 表示有符号整型
p = mp.Process(target=modify, args=(num, arr))
p.start()
p.join()
print(num.value) # 输出: 3.14
print(arr[:]) # 输出: [0, 2, 4, 6, 8]
五、进程同步:别打架!
多进程最大的难点之一,就是同步。 比如多个进程同时修改一个共享变量,很可能出问题。
解决办法:锁、信号量、条件变量。
这里重点给你演示锁的用法:
scss
def increment(lock, counter):
for _ in range(1000):
with lock:
counter.value += 1
if __name__ =='__main__':
lock = mp.Lock()
counter = mp.Value('i', 0)
processes = [mp.Process(target=increment, args=(lock, counter)) for _ in range(4)]
for p in processes: p.start()
for p in processes: p.join()
print("最终值:", counter.value) # 正确输出 4000
如果不用锁,你会发现结果完全不对。这就是"线程安全"同样存在于进程中的原因。
六、进程池:批量任务的神器
如果你需要一次性处理很多任务,比如计算一堆平方数,用进程池就很方便:
python
def square(x):
return x * x
if __name__ =='__main__':
with mp.Pool(processes=4) as pool: # 创建4个工作进程
results = pool.map(square, range(10))
print("map结果:", results) # [0, 1, 4, 9, ..., 81]
async_result = pool.apply_async(square, (5,))
print("异步结果:", async_result.get()) # 25
for res in pool.imap(square, range(5)):
print("迭代结果:", res)
相比自己管理进程,Pool
更轻松,尤其是需要反复执行任务的情况。
七、实战:生产者-消费者模型
最后给你一个完整例子,经典的"生产-消费"模型:
python
import multiprocessing as mp
import time
def producer(queue, items):
for item in items:
print(f"生产: {item}")
queue.put(item)
time.sleep(0.1)
queue.put(None) # 结束信号
def consumer(queue):
while True:
item = queue.get()
if item is None: break
print(f"消费: {item}")
time.sleep(0.2)
if __name__ =='__main__':
queue = mp.Queue(maxsize=3) # 限制队列大小
items =[f"产品-{i}" for i in range(10)]
prod = mp.Process(target=producer, args=(queue, items))
cons = mp.Process(target=consumer, args=(queue,))
prod.start()
cons.start()
prod.join()
cons.join()
print("生产消费完成")
这个例子一看就能用到实战里,比如日志处理、任务调度等。
八、踩坑经验分享
- Windows 必须写
if __name__ == '__main__'
,否则会无限创建进程。 - 不要滥用共享内存,能用消息队列就别用锁。
- 记得
.join()
,否则容易变成僵尸进程。 - 多进程调试麻烦,最好模块化开发,逐步加复杂度。
- 大任务拆小块,通信尽量少,否则传输开销可能大过运算。
九、总结
如果你能把本文的例子都敲一遍,就已经基本掌握 Python 多进程编程了。 简单回顾一下:
- 为什么要多进程:突破 GIL,利用多核 CPU。
- 基础用法 :
Process
、类继承。 - 进程通信:Queue / Pipe / Value。
- 进程同步:Lock / Semaphore / Condition。
- 进程池:批量任务利器。
- 最佳实践:生产者-消费者模型。
一句话总结: 👉 CPU 密集型任务就用多进程,I/O 密集型任务就用多线程。
掌握这套方法,你写 Python 程序的速度,真的能"起飞"。