摘要
在Python多进程编程中,高效且安全地共享数据是一个常见需求。本文将通过一个完整的示例,初步探讨如何使用multiprocessing.shared_memory模块实现进程间数据的共享,并通过同步原语 确保数据一致性和资源安全释放。
共享内存基础:为什么需要它?
在多进程环境中,每个进程都有自己独立的内存空间。传统的变量和数据结构无法再进程间共享,这就导致了进程间通信(IPC)的需求。虽然Python提供了诸如队列(Queue)、管道(Pipe)等IPC机制,但对于大数据量的共享,这些方法的性能可能不够理想。
共享内存(Shared Memory)是一种高效的IPC方式,它允许不同进程访问同一块物理内存区域。通过共享内存,过个进程可以直接读写同一份数据,避免了数据复制的开销,显著提高了数据传输的效率。
在Python中,我们可以使用multiprocessing.shared_memory模块来实现共享内存功能。下面是一个完整示例
import numpy as np
from multiprocessing import Process, Manager
from multiprocessing.shared_memory import SharedMemory
import time
import random
def create_shared_memory(size, dtype=np.int32, name=None):
"""创建共享内存并返回共享内存对象和NumPy数组视图"""
# 创建一个本地NumPy数组作为模板
arr = np.zeros(size, dtype=dtype)
# 创建共享内存块,指定名称或自动生成
shm = SharedMemory(create=True, size=arr.nbytes, name=name)
# 在共享内存上创建NumPy数组视图
shared_arr = np.ndarray(arr.shape, dtype=arr.dtype, buffer=shm.buf)
# 将本地数组的值复制到共享内存
np.copyto(shared_arr, arr)
return shm, shared_arr
def writer_process(shm_name, shape, dtype, barrier, counter):
"""写入进程:向共享内存写入随机数据"""
try:
# 连接到已存在的共享内存
shm = SharedMemory(name=shm_name)
# 创建共享内存上的NumPy数组视图
shared_arr = np.ndarray(shape, dtype=dtype, buffer=shm.buf)
# 等待所有进程准备好
barrier.wait()
print("写入进程: 开始工作")
for i in range(5):
# 模拟写入操作(写入随机整数)
value = random.randint(100, 999)
shared_arr[0] = value
print(f"写入进程: 在位置0写入值 {value}")
# 增加计数器,表示完成一次写入
counter.value += 1
# 随机等待一段时间,模拟处理延迟
time.sleep(10)
# 关闭共享内存连接
shm.close()
print("写入进程: 已完成")
except Exception as e:
print(f"写入进程错误: {e}")
def reader_process(shm_name, shape, dtype, barrier, counter, exit_event):
"""读取进程:从共享内存读取数据并验证"""
try:
# 连接到已存在的共享内存
shm = SharedMemory(name=shm_name)
# 创建共享内存上的NumPy数组视图
shared_arr = np.ndarray(shape, dtype=dtype, buffer=shm.buf)
# 等待所有进程准备好
barrier.wait()
print("读取进程: 开始工作")
last_value = None
read_count = 0
while not exit_event.is_set():
# 检查计数器,只有当有新写入时才读取
current_count = counter.value
if current_count > read_count:
current_value = shared_arr[0]
if current_value != last_value:
print(f"读取进程: 在位置0读取到新值 {current_value}")
last_value = current_value
read_count = current_count
# 短暂休眠,减少CPU使用率
time.sleep(0.1)
# 关闭共享内存连接
shm.close()
print("读取进程: 已完成")
except Exception as e:
print(f"读取进程错误: {e}")
def main():
try:
# 创建管理器,用于进程间同步
with Manager() as manager:
# 创建屏障,确保所有进程同时开始
barrier = manager.Barrier(2) # 2个工作进程 + 1个主进程
# 创建计数器,记录写入次数
counter = manager.Value('i', 0)
# 创建退出事件,通知读取进程何时停止
exit_event = manager.Event()
# 创建共享内存(1个整数大小)
shm, shared_arr = create_shared_memory(1, np.int32, "my_shared_memory")
print(f"主进程: 共享内存已创建,名称为 '{shm.name}'")
# 创建并启动写入进程
writer = Process(target=writer_process,
args=(shm.name, shared_arr.shape, shared_arr.dtype, barrier, counter))
writer.start()
# 创建并启动读取进程
reader = Process(target=reader_process,
args=(shm.name, shared_arr.shape, shared_arr.dtype, barrier, counter, exit_event))
reader.start()
# 等待写入进程完成
writer.join()
# 通知读取进程可以退出
exit_event.set()
# 等待读取进程完成
reader.join()
# 关闭并释放共享内存
shm.close()
shm.unlink()
print("主进程: 共享内存已释放")
except KeyboardInterrupt:
print("\n程序被用户中断")
except Exception as e:
print(f"主进程错误: {e}")
if __name__ == "__main__":
main()
代码解析
这个示例实现了一个简单的生产者-消费者模型,执行输出是这样的
代码里有几块说明
- 写入进程(生产者):向共享内存中写入随机数
- 读取进程(消费者):从共享内存中读取数据,并在检测到新的值时打印
- 主进程:负责创建共享内存、启动子进程、并协调他们的执行顺序。
同步原语详解
1、Barrier(屏障)
Barrier用于让多个进程在某个执行点上互相等待,直到所有进程都达到该点才继续执行,在示例中
barrier = manager.Barrier(2) # 需要2个进程到达屏障点
每个进程通过调用barrier.wait()进入等待状态,当所有2个进程都调用了wait()后,它们会同时被释放,继续执行后续代码。这确保了所有进程在开始读写共享内存前都已就绪
2、Event(事件)
Event是一个简单的标志位,用于进程间通信。在示例中,我们使用exit_event通知读取进程何时停止工作。
exit_event = manager.Event() # 初始状态为False
# 读取进程中
while not exit_event.is_set(): # 持续读取直到事件被设置
# 读取共享内存数据
# 主进程中
exit_event.set() # 设置事件标志为True,通知读取进程退出
这种机制允许主进程在适当时机优雅地终止读取进程,避免强制终止可能导致的资源泄露。
3、join()方法
join()方法用于阻塞当前进程,直到被调用的子进程执行完毕。在示例中,我们按照顺序join()
writer.join() # 等待写入进程完成
exit_event.set() # 写入完成后,通知读取进程退出
reader.join() # 等待读取进程完成
这个顺序非常重要,原因如下
- 先等待写入进程,确保所有数据都已写入共享内存,保证数据完整性
- 再通知读取进程退出,确保读取进程有机会处理所有数据
- 最后等待读取进程,确保读取进程完成资源清理工作,避免资源泄露
4、Manager的功能
Manager是一个高级的进程间通信工具,它通过启动一个独立的管理进程,来协调多个工作进程之间的共享状态。主要功能包括。
1、创建共享数据结构
- list:共享列表
- dict: 共享字典
- Namespace: 共享命名空间对象
2、提供同步元语
- Lock: 互斥锁
- RLock:可重入锁
- Semaphore: 信号量
- BoundSemaphore: 有界信号量
- condition: 条件变量
- Event: 时间
- Barrier: 屏障
3、数值和字符串共享
- value:共享单个值
- Array: 共享数组