基于匿名mmap的进程间通信:如何避免轮询实现变更通知?
在Windows下监听mmap内存变更的方案
因为mmap本身没有内置的内容变更通知机制,在Windows平台上,你可以结合命名事件对象来实现“写变更后通知读进程”的逻辑,完全不需要依赖磁盘文件,下面是具体实现思路和代码:
核心思路
利用Windows原生的命名事件(Event)做同步:
- 写进程更新mmap内容后,触发事件通知读进程
- 读进程在单独线程中等待事件信号,收到信号后立即读取mmap内容,然后重置事件等待下一次通知
具体实现代码
1. 封装事件操作(用ctypes调用Win32 API,无需额外依赖)
import ctypes from ctypes import wintypes # 加载kernel32.dll kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) # 定义Win32 API的参数类型 kernel32.CreateEventW.argtypes = [wintypes.LPCVOID, wintypes.BOOL, wintypes.BOOL, wintypes.LPCWSTR] kernel32.CreateEventW.restype = wintypes.HANDLE kernel32.SetEvent.argtypes = [wintypes.HANDLE] kernel32.SetEvent.restype = wintypes.BOOL kernel32.ResetEvent.argtypes = [wintypes.HANDLE] kernel32.ResetEvent.restype = wintypes.BOOL kernel32.WaitForSingleObject.argtypes = [wintypes.HANDLE, wintypes.DWORD] kernel32.WaitForSingleObject.restype = wintypes.DWORD kernel32.CloseHandle.argtypes = [wintypes.HANDLE] kernel32.CloseHandle.restype = wintypes.BOOL # 常量定义 WAIT_OBJECT_0 = 0 INFINITE = 0xFFFFFFFF class SharedEvent: def __init__(self, event_name: str): # 创建或打开命名事件,跨进程可见 self.h_event = kernel32.CreateEventW(None, True, False, event_name) if not self.h_event: raise ctypes.WinError(ctypes.get_last_error()) def set(self): # 触发事件 if not kernel32.SetEvent(self.h_event): raise ctypes.WinError(ctypes.get_last_error()) def reset(self): # 重置事件为未触发状态 if not kernel32.ResetEvent(self.h_event): raise ctypes.WinError(ctypes.get_last_error()) def wait(self): # 无限等待事件触发 result = kernel32.WaitForSingleObject(self.h_event, INFINITE) if result != WAIT_OBJECT_0: raise ctypes.WinError(ctypes.get_last_error()) def close(self): # 关闭事件句柄 if self.h_event: kernel32.CloseHandle(self.h_event) self.h_event = None def __del__(self): self.close()
2. 写进程代码示例(更新mmap后触发事件)
import mmap MMAP_SIZE = 4 # 小整数用4字节足够(比如int32) SHARED_EVENT_NAME = "MySharedEvent" SHARED_MMAP_NAME = "my_shared_variable" def write_shared_value(value: int): # 打开mmap my_mmap = mmap.mmap(-1, MMAP_SIZE, SHARED_MMAP_NAME) # 打开事件 event = SharedEvent(SHARED_EVENT_NAME) try: # 写入整数(转为4字节小端字节流) my_mmap.seek(0) my_mmap.write(value.to_bytes(4, byteorder='little')) # 触发事件通知读进程 event.set() finally: my_mmap.close() event.close() # 测试:写入值100 write_shared_value(100)
3. 读进程代码示例(Qt线程中等待事件并读取)
因为PySide6的主线程不能阻塞,所以把事件等待放到QThread中:
import mmap from PySide6.QtCore import QThread, Signal from PySide6.QtWidgets import QApplication, QLabel MMAP_SIZE = 4 SHARED_EVENT_NAME = "MySharedEvent" SHARED_MMAP_NAME = "my_shared_variable" class SharedValueReader(QThread): value_updated = Signal(int) # 定义更新信号,传递读取到的整数 def __init__(self): super().__init__() self._running = True def run(self): # 打开mmap和事件 my_mmap = mmap.mmap(-1, MMAP_SIZE, SHARED_MMAP_NAME) event = SharedEvent(SHARED_EVENT_NAME) try: while self._running: # 等待事件触发 event.wait() # 读取整数 my_mmap.seek(0) data = my_mmap.read(4) value = int.from_bytes(data, byteorder='little') # 发送信号给主线程更新UI self.value_updated.emit(value) # 重置事件,等待下一次通知 event.reset() finally: my_mmap.close() event.close() def stop(self): self._running = False # 可以手动设置事件让线程退出等待循环 event = SharedEvent(SHARED_EVENT_NAME) event.set() event.close() if __name__ == "__main__": app = QApplication([]) label = QLabel("等待共享变量更新...") label.show() reader = SharedValueReader() reader.value_updated.connect(lambda v: label.setText(f"当前共享值:{v}")) reader.start() app.exec() reader.stop() reader.wait()
注意事项
- 事件名称和mmap名称要唯一,避免和系统中其他进程的命名冲突
- 整数的字节序要统一(这里用小端,和Windows系统一致)
- 读进程的线程要正确处理停止逻辑,避免资源泄漏
- 如果需要支持多写进程,可能需要额外的互斥锁(Mutex)来保护mmap的写入操作
内容的提问来源于stack exchange,提问作者bers
相关产品推荐
相关产品推荐

