深入理解Python的进程间通信与并发模式

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()

模型优化

  1. 使用JoinableQueue替代Queue,可以更好地跟踪任务完成情况

  2. 设置消费者为守护进程,主进程结束时自动退出

  3. 合理设置缓冲区大小,平衡内存使用和性能

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]

注意事项

  1. Manager会降低性能,因为它需要额外的进程间通信

  2. 对共享数据的操作不是原子性的,必要时仍需加锁

  3. 优先考虑使用队列而不是共享数据

4. 进程池(multiprocessing.Pool)

进程池用于管理固定数量的工作进程,适合处理大量小任务。

为什么需要进程池

  1. 避免频繁创建销毁进程的开销

  2. 控制并发进程数量,防止系统过载

  3. 简化进程管理

进程池基本使用

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多进程编程的高级特性,包括:

  1. 进程间通信:队列和JoinableQueue的使用

  2. 生产者消费者模型:经典并发模式实现

  3. 数据共享:Manager类的安全共享方式

  4. 进程池:高效管理大量小任务

  5. 回调函数:异步处理任务结果

多进程编程可以充分利用多核CPU资源,但需要注意:

  • 尽量避免共享数据,使用消息传递

  • 合理控制并发进程数量

  • 处理好进程同步和通信问题

  • 根据任务特点选择合适的多进程模型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值