目录
1. 引言:多进程编程的意义
Python的multiprocessing
模块通过多进程机制充分利用多核CPU资源,适用于计算密集型任务。本文将通过队列通信、数据共享、进程池等核心模块,结合真实代码案例,带你从入门到实战,彻底掌握多进程编程的精髓!
2. 队列(Queue):进程间通信的核心
队列是多进程间数据传递的“管道”,multiprocessing.Queue
提供线程安全的操作。
2.1 阻塞与非阻塞操作
-
阻塞方法:
put()
和get()
在队列满/空时自动阻塞,适合稳定流量场景。 -
非阻塞方法:
put_nowait()
和get_nowait()
直接抛出异常,需结合异常处理逻辑。
from multiprocessing import Queue
# 示例:阻塞式队列操作
q = Queue(3)
q.put("A") # 正常插入
print(q.get()) # 输出:A
# 非阻塞操作需捕获异常
try:
q.put_nowait("D") # 队列满时抛出queue.Full
except Exception as e:
print("队列已满!")
2.2 生产者消费者模型的两种实现
场景:生产者生成数据,消费者处理数据,通过队列解耦两者。
方案一:Queue + Lock
-
锁机制:避免多个进程同时操作队列导致竞争。
from multiprocessing import Process, Queue, Lock
def producer(queue, food, lock):
while True:
lock.acquire()
item = f"制作:{food}"
queue.put(item)
print(item)
lock.release()
def consumer(queue, name, lock):
while True:
lock.acquire()
item = queue.get()
print(f"{name}消费:{item}")
lock.release()
方案二:JoinableQueue自动同步
-
优势:通过
task_done()
和join()
自动协调生产消费节奏。
from multiprocessing import JoinableQueue
def producer(q, food):
q.put(food)
q.join() # 等待消费者处理完成
def consumer(q, name):
item = q.get()
print(f"{name}处理:{item}")
q.task_done() # 标记任务完成
对比总结:
方案 | 优点 | 缺点 |
---|---|---|
Queue + Lock | 灵活控制锁粒度 | 需手动处理同步逻辑 |
JoinableQueue | 自动同步,代码简洁 | 适用场景较固定 |
3. 数据共享与同步:Manager模块的妙用
默认情况下,多进程无法共享内存,需通过Manager
创建共享对象(如列表、字典)。
from multiprocessing import Process, Manager
def update_data(shared_list):
shared_list[0] = 100 # 修改共享列表
if __name__ == "__main__":
manager = Manager()
shared_list = manager.list([1, 2, 3])
p = Process(target=update_data, args=(shared_list,))
p.start()
p.join()
print("共享列表结果:", shared_list) # 输出:[100, 2, 3]
应用场景:多进程协同处理同一数据集(如分布式计算)。
4. 进程池(Pool):高效资源管理
进程池用于批量管理子进程,避免频繁创建/销毁进程的开销。
4.1 同步 vs 异步执行
-
同步(
apply
):任务依次执行,适合依赖前序结果的场景。 -
异步(
apply_async
):任务并行执行,提升吞吐量。
from multiprocessing import Pool
import time
def task(n):
time.sleep(1)
return n * 2
# 同步示例
with Pool(4) as pool:
result = pool.apply(task, (5,)) # 阻塞直到任务完成
print("同步结果:", result) # 输出:10
# 异步示例
with Pool(4) as pool:
async_result = pool.apply_async(task, (10,))
print("异步结果:", async_result.get()) # 输出:20(需等待1秒)
4.2 进程池在爬虫中的应用
场景:并发请求多个URL,加速数据抓取。
import requests
from multiprocessing import Pool
def crawl(url):
response = requests.get(url)
return f"{url} 状态码:{response.status_code}"
if __name__ == "__main__":
urls = ["https://www.baidu.com", "https://www.google.com"]
with Pool(3) as p:
results = p.map(crawl, urls) # 并发执行
print(results)
5. 综合实战:生产者消费者模型优化
结合进程池与队列,实现高效任务分发:
from multiprocessing import Pool, JoinableQueue
def producer(queue, data):
for item in data:
queue.put(item)
def consumer(queue):
while True:
item = queue.get()
print(f"处理:{item}")
queue.task_done()
if __name__ == "__main__":
q = JoinableQueue()
data = [f"任务{i}" for i in range(10)]
# 进程池启动消费者
with Pool(4) as pool:
pool.apply_async(consumer, (q,))
producer(q, data)
q.join() # 等待所有任务完成
6. 总结与进阶学习
-
核心知识点:
-
队列是多进程通信的基础,生产者消费者模型是典型设计模式。
-
Manager
模块解决数据共享问题,进程池提升资源利用率。
-
-
扩展方向:
-
学习
concurrent.futures
模块的线程池/进程池高级接口。 -
探索
asyncio
协程在I/O密集型任务中的应用。
-
7. 附录:常见问题与扩展练习
Q1:多进程 vs 多线程如何选择?
-
CPU密集型:多进程(避免GIL限制)。
-
I/O密集型:多线程或协程。
Q2:进程池的最大工作进程数如何设置?
推荐值为CPU核心数+1,根据任务类型调整。