'unlink()' 在 Python 的 shared_memory 和 Windows 中不起作用

'unlink()' does not work in Python's shared_memory on Windows

我正在使用 Python 3.8 的新 shared_memory 模块,但无法在不终止使用它的进程的情况下释放共享内存。

创建并使用共享内存块 shm 后,我在所有进程中通过 shm.close() 关闭它,最后在主进程中通过 shm.unlink 释放它。然而,资源监视器显示内存 没有释放 直到程序终止。这对我来说是个严重的问题,因为我的程序需要 运行 很长时间。可以使用以下程序在 Windows/Python 3.8 上重现该问题:

from multiprocessing import shared_memory, Pool
from itertools import repeat
from time import sleep

def fun(dummy, name):
    
    # access shared memory
    shm = shared_memory.SharedMemory(name=name)
    
    # do work
    sleep(1)
    
    # release shared memory
    shm.close()
    
    return dummy

def meta_fun(pool):
    
    # create shared array
    arr = shared_memory.SharedMemory(create=True, size=500000000)
    
    # compute result
    result = sum(pool.starmap(fun, zip(range(10), repeat(arr.name))))
    
    # release and free memory
    arr.close()
    arr.unlink()
    
    return result

if __name__ == '__main__':
    
    # use one Pool for many method calls to save the time for repeatedly
    # creating processes
    with Pool() as pool:
        for i in range(100):
            print(meta_fun(pool))

注意:执行此脚本时,您可能会很快填满整个内存!观察资源监视器中的“虚拟内存”面板。

经过一些研究,我发现 (1) unlink() 函数 does nothing on Windows:

def unlink(self):
    """Requests that the underlying shared memory block be destroyed.
    In order to ensure proper cleanup of resources, unlink should be
    called once (and only once) across all processes which have access
    to the shared memory block."""
    if _USE_POSIX and self._name:
        from .resource_tracker import unregister
        _posixshmem.shm_unlink(self._name)
        unregister(self._name, "shared_memory")

和 (2) Windows 似乎在 created/used 进程停止后释放共享内存(参见评论 here and here)。这可能是 Python 未明确处理此问题的原因。

作为回应,我通过反复保存和重复使用同一个共享内存块而不取消链接来构建一个丑陋的解决方法。显然,这不是一个令人满意的解决方案,尤其是当所需内存块的大小动态变化时。

有没有办法手动释放 Windows 上的共享内存?

这是 multiprocessing 模块中的一个错误,报告为 Issue 40882. There is an open pull request that fixes it, PR 20684,尽管合并速度很慢。

错误如下:在 SharedMemory.__init__ 中,我们有 an invocation of the MapViewOfFile API without a corresponding UnmapViewOfFile,并且 mmap 对象也没有取得它的所有权(它自己再次映射块).

与此同时,您可以对 shared_memory 模块进行猴子修补,以便在 mmap 构建之后添加缺少的 UnmapViewOfFile 调用。您可能不得不依赖 ctypes,因为 _winapi 模块不导出 UnmapViewOfFile,尽管导出 MapViewOfFile (!)。像这样(未测试):

import ctypes, ctypes.wintypes
import multiprocessing, multiprocessing.shared_memory

UnmapViewOfFile = ctypes.windll.kernel32.UnmapViewOfFile
UnmapViewOfFile.argtypes = (ctypes.wintypes.LPCVOID,)
UnmapViewOfFile.restype = ctypes.wintypes.BOOL

def _SharedMemory_init(self, name=None, create=False, size=0):
    ... # copy from SharedMemory.__init__ in the original module
                try:
                    p_buf = _winapi.MapViewOfFile(
                        h_map,
                        _winapi.FILE_MAP_READ,
                        0,
                        0,
                        0
                    )
                finally:
                    _winapi.CloseHandle(h_map)
                try:
                    size = _winapi.VirtualQuerySize(p_buf)
                    self._mmap = mmap.mmap(-1, size, tagname=name)
                finally:
                    UnmapViewOfFile(p_buf)
    ... # copy from SharedMemory.__init__ in the original module

multiprocessing.shared_memory.SharedMemory.__init__ = _SharedMemory_init

将上面的代码放入一个模块中,并记得在使用 multiprocessing 模块中的任何内容之前加载它。或者,您可以直接编辑 multiprocessing 模块目录中的 shared_memory.py 文件以包含所需的 UnmapViewOfFile 调用。这不是最干净的解决方案,但无论如何它都是暂时的(著名的遗言);长期的解决方案是让这个固定的上游(显然正在进行中)。