You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于匿名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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 11:25:22