1. 进程间通信(IPC)
进程间通信是多进程编程的核心问题之一。Python提供了多种IPC机制,其中最常用的是队列(Queue)。
1.1 队列(Queue)
基本概念
-
FIFO原则:先进先出(First In First Out)
-
线程安全:Queue是多进程安全的
-
底层实现:基于管道和锁实现
队列常用方法
方法 | 描述 | 阻塞行为 |
---|---|---|
q.put(item) | 放入项目 | 如果队列满则阻塞 |
q.get() | 获取项目 | 如果队列空则阻塞 |
q.put_nowait(item) | 放入项目 | 如果队列满则抛出Queue.Full异常 |
q.get_nowait() | 获取项目 | 如果队列空则抛出Queue.Empty异常 |
q.empty() | 判断是否空 | 结果可能不可靠 |
q.full() | 判断是否满 | 结果可能不可靠 |
q.qsize() | 返回队列大小 | 结果可能不可靠 |
队列使用示例
from multiprocessing import Queue
q = Queue(5) # 创建最大容量为5的队列
# 非阻塞方式放入元素
q.put_nowait("鱼香肉丝")
q.put_nowait("乌鸡枸杞汤")
q.put_nowait("地三鲜")
q.put_nowait("油焖大虾")
q.put_nowait("饺子")
# 尝试放入第6个元素会抛出Queue.Full异常
# q.put_nowait("馒头") # 取消注释会报错
print("放置结束")
# 获取元素
print(q.get()) # 鱼香肉丝
print(q.get()) # 乌鸡枸杞汤
1.2 JoinableQueue
JoinableQueue是Queue的子类,增加了任务完成跟踪功能。
新增方法
-
q.task_done()
: 消费者调用,表示一个任务完成 -
q.join()
: 生产者调用,等待所有任务完成
JoinableQueue示例
from multiprocessing import Process, JoinableQueue
import time
def producer(q, food):
while True:
item = "厨师在后厨制作了%s" % food
q.put(item)
print(item)
time.sleep(1)
q.join() # 等待所有任务完成
def consumer(q, name):
while True:
item = q.get()
print("%s 从吧台拿走了 %s" % (name, item))
time.sleep(1)
q.task_done() # 标记任务完成
if __name__ == '__main__':
q = JoinableQueue(1)
p1 = Process(target=producer, args=(q, "蛋炒面"))
p2 = Process(target=producer, args=(q, "白色的棉花糖"))
p3 = Process(target=consumer, args=(q, "吃货路飞"))
p4 = Process(target=consumer, args=(q, "船医乔巴"))
# 设置消费者为守护进程
p3.daemon = True
p4.daemon = True
p1.start()
p2.start()
p3.start()
p4.start()
p1.join()
p2.join()
2. 生产者消费者模型
生产者消费者模型是多进程编程中的经典模式,用于解耦生产数据和消费数据的过程。
基本概念
-
生产者:产生数据的进程
-
消费者:处理数据的进程
-
缓冲区:通常是队列,平衡生产和消费速度差异
实现示例
from multiprocessing import Process, Queue, Lock
import time
def producer(q, l, food):
while True:
l.acquire() # 获取锁
item = "厨师在后厨制作了%s" % food
q.put(item)
print(item)
time.sleep(1)
l.release() # 释放锁
def consumer(q, l, name):
while True:
l.acquire()
item = q.get()
print("%s 从吧台拿走了 %s" % (name, item))
time.sleep(1)
l.release()
if __name__ == '__main__':
q = Queue(2) # 缓冲区大小为2
l = Lock() # 创建锁
# 创建2个生产者和2个消费者
p1 = Process(target=producer, args=(q, l, "蛋炒面"))
p2 = Process(target=producer, args=(q, l, "白色的棉花糖"))
p3 = Process(target=consumer, args=(q, l, "吃货路飞"))
p4 = Process(target=consumer, args=(q, l, "船医乔巴"))
p1.start()
p2.start()
p3.start()
p4.start()
模型优化
-
使用JoinableQueue替代Queue,可以更好地跟踪任务完成情况
-
设置消费者为守护进程,主进程结束时自动退出
-
合理设置缓冲区大小,平衡内存使用和性能
3. 进程间数据共享
虽然进程间通常应该避免共享数据,但Python提供了Manager类来实现安全的进程间数据共享。
Manager使用示例
from multiprocessing import Manager, Process
def modify_data(num):
num[0] = 78 # 修改共享数据
if __name__ == '__main__':
m = Manager() # 创建Manager对象
num = m.list([1, 2, 3, 4, 5, 6]) # 创建共享列表
p = Process(target=modify_data, args=(num,))
p.start()
p.join()
print(num) # 输出: [78, 2, 3, 4, 5, 6]
注意事项
-
Manager会降低性能,因为它需要额外的进程间通信
-
对共享数据的操作不是原子性的,必要时仍需加锁
-
优先考虑使用队列而不是共享数据
4. 进程池(multiprocessing.Pool)
进程池用于管理固定数量的工作进程,适合处理大量小任务。
为什么需要进程池
-
避免频繁创建销毁进程的开销
-
控制并发进程数量,防止系统过载
-
简化进程管理
进程池基本使用
from multiprocessing import Pool
import os
import time
def work(n):
print('%s run' % os.getpid())
time.sleep(3)
return n ** 2
if __name__ == '__main__':
p = Pool(3) # 创建包含3个进程的进程池
# 同步调用
res = p.apply(work, args=(2,)) # 同步执行,阻塞直到结果返回
print(res) # 输出: 4
# 异步调用
result = p.apply_async(work, args=(3,)) # 异步执行
print(result.get()) # 输出: 9,get()会阻塞直到结果就绪
p.close() # 关闭进程池,不再接受新任务
p.join() # 等待所有子进程完成
进程池与多进程效率对比
from multiprocessing import Pool, Process
import os
import time
def func01(num):
num += 1
if __name__ == '__main__':
start = time.time()
p = Pool(os.cpu_count() + 1) # 进程池
# 使用进程池
for i in range(100):
res = p.apply_async(func=func01, args=(i,))
p.close()
p.join()
print("进程池执行时间:", time.time() - start)
# 使用普通多进程
start = time.time()
plist = []
for i in range(100):
p = Process(target=func01, args=(i,))
plist.append(p)
p.start()
[p.join() for p in plist]
print("多进程执行时间:", time.time() - start)
进程池方法总结
方法 | 描述 | 同步/异步 |
---|---|---|
p.apply(func) | 同步执行函数 | 同步 |
p.apply_async(func) | 异步执行函数 | 异步 |
p.map(func, iterable) | 并行映射函数到可迭代对象 | 同步 |
p.map_async(func, iterable) | 异步并行映射 | 异步 |
p.close() | 关闭进程池 | - |
p.join() | 等待所有进程完成 | - |
5. 回调函数
进程池支持回调函数,当任务完成时会自动调用回调函数处理结果。
from multiprocessing import Pool
import os
def work(n):
print('%s run' % os.getpid())
return n ** 2
def callback(result):
print('处理结果:', result)
if __name__ == '__main__':
p = Pool(3)
# 异步调用并指定回调函数
p.apply_async(work, args=(4,), callback=callback)
p.close()
p.join()
总结
本文详细介绍了Python多进程编程的高级特性,包括:
-
进程间通信:队列和JoinableQueue的使用
-
生产者消费者模型:经典并发模式实现
-
数据共享:Manager类的安全共享方式
-
进程池:高效管理大量小任务
-
回调函数:异步处理任务结果
多进程编程可以充分利用多核CPU资源,但需要注意:
-
尽量避免共享数据,使用消息传递
-
合理控制并发进程数量
-
处理好进程同步和通信问题
-
根据任务特点选择合适的多进程模型