在Windows平台上,Python的shared_memory中的'unlink()'无法正常工作。

4
我正在使用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()函数在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 似乎会在创建/使用共享内存的进程停止后释放它(请参见这里这里的评论)。这可能是 Python 没有显式处理此问题的原因。
为此,我建立了一个丑陋的解决方法,通过反复保存和重用同一共享内存块而不断开链接。显然,这不是一个令人满意的解决方案,特别是当所需内存块的大小动态变化时。
有没有办法在 Windows 上手动释放共享内存?
1个回答

8
这是模块中的一个bug,已报告为问题40882。有一个已开放的拉取请求PR 20684修复了它,但显然合并速度很慢。
该bug如下:在中,我们调用了一个API, MapViewOfFile,没有相应的UnmapViewOfFile,而mmap对象也没有拥有它(它自己再次映射该块)。

与此同时,您可以对shared_memory模块进行猴子补丁,以便在构建mmap之后添加缺失的UnmapViewOfFile调用。您可能需要依赖于ctypes,因为_winapi模块尽管导出了MapViewOfFile,但并未导出UnmapViewOfFile。类似于这样(未经测试):

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调用。这不是最干净的解决方案,但它只是暂时的(著名的最后一句话); 长期解决方案是在上游进行修复(显然正在进行中)。

1
我已经测试过了。像魔法一样好用! - Samufi
2
这个修复现在似乎已经被纳入了Python 3.10.9和3.11.1版本的发布中。 - Matt Pitkin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接