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

Python 3.8 shared_memory模块在Windows系统中unlink()无法释放共享内存的问题求助

Windows下Python shared_memory无法手动释放内存的问题解决

Hey,我之前也碰到过Windows下Python共享内存回收的坑,结合你描述的情况和对Python源码、Windows系统机制的分析,咱们来一步步解决这个问题:

问题拆解

你现在的核心问题是:用Python 3.8的shared_memory模块创建共享内存后,即使在所有进程调用shm.close()、主进程调用shm.unlink(),内存还是要等整个程序终止才会释放,这对长期运行的服务来说确实是大问题。你提供的复现代码也精准命中了这个场景——复用进程池时,共享内存资源无法被及时回收。

为什么会这样?

看shared_memory的源码就懂了,unlink()只有在POSIX系统(比如Linux、macOS)下才会真正执行共享内存的销毁逻辑,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")

也就是说,你在Windows上调用arr.unlink(),除了注册一下资源追踪,根本没做实际的内存释放操作。

2. Windows的共享内存回收机制

Windows的共享内存(文件映射对象)默认是所有关联进程都关闭句柄后才会被系统回收,而你复用进程池的话,进程一直存活,它们虽然调用了shm.close(),但系统层面可能还有残留的句柄引用,或者Python的资源追踪没处理干净,导致内存一直占着。

可行的解决方案

方案1:用Windows原生API强制清理(推荐)

既然Python的unlink()不给力,咱们直接调用Windows的系统API来手动释放共享内存。需要先装pywin32库:

pip install pywin32

然后修改你的代码,在arr.unlink()之后添加手动释放的逻辑:

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

def fun(dummy, name):
    shm = shared_memory.SharedMemory(name=name)
    sleep(1)
    shm.close()
    return dummy

def meta_fun(pool):
    arr = shared_memory.SharedMemory(create=True, size=500000000)
    result = sum(pool.starmap(fun, zip(range(10), repeat(arr.name))))
    arr.close()
    arr.unlink()
    
    # 手动调用Windows API释放共享内存
    try:
        # 打开对应的文件映射对象
        mapping_handle = win32file.CreateFileMapping(
            win32api.INVALID_HANDLE_VALUE,
            None,
            win32file.PAGE_READWRITE,
            0,
            0,
            arr.name
        )
        if mapping_handle:
            # 设置映射大小为0,标记为待删除
            win32file.SetFileMappingSize(mapping_handle, 0)
            win32api.CloseHandle(mapping_handle)
            print(f"共享内存 {arr.name} 已手动释放")
    except Exception as e:
        print(f"手动释放失败: {str(e)}")
    
    return result

if __name__ == '__main__':
    with Pool() as pool:
        for i in range(100):
            print(f"第{i+1}次执行结果: {meta_fun(pool)}")

这个方案的核心是通过CreateFileMapping获取共享内存的句柄,然后用SetFileMappingSize标记它为待删除,最后关闭句柄,这样Windows就会立即回收内存,不用等进程终止。

方案2:复用大块共享内存(适合动态大小场景)

如果你的内存需求是动态变化的,不想频繁创建销毁共享内存,可以预先申请一块足够大的共享内存,然后在内部自己管理内存分区(比如用偏移量标记已使用/空闲区域)。这种方式虽然需要额外的内存管理逻辑,但能彻底避免内存泄漏:

class DynamicSharedMemory:
    def __init__(self, max_total_size):
        self.shm = shared_memory.SharedMemory(create=True, size=max_total_size)
        self.free_blocks = [(0, max_total_size)]  # 存储空闲区域的(起始偏移, 大小)
    
    def allocate(self, need_size):
        # 简单的首次适配算法找空闲块
        for idx, (start, size) in enumerate(self.free_blocks):
            if size >= need_size:
                self.free_blocks.pop(idx)
                if size > need_size:
                    self.free_blocks.append((start + need_size, size - need_size))
                return start, need_size
        raise ValueError("内存不足,无法分配")
    
    def free(self, start, size):
        # 释放后合并相邻空闲块(可选,简化逻辑可以直接添加)
        self.free_blocks.append((start, size))
    
    def close(self):
        self.shm.close()
        self.shm.unlink()

# 使用示例
def fun(dummy, shm_name, offset, size):
    shm = shared_memory.SharedMemory(name=shm_name)
    # 通过偏移量访问对应区域
    data = shm.buf[offset:offset+size]
    sleep(1)
    shm.close()
    return dummy

方案3:放弃进程池复用(简单粗暴)

如果进程创建的开销对你来说可以接受,那每次执行meta_fun时都重新创建进程池,这样任务完成后进程池终止,所有关联的共享内存句柄都会被系统回收:

def meta_fun():
    arr = shared_memory.SharedMemory(create=True, size=500000000)
    with Pool() as pool:
        result = sum(pool.starmap(fun, zip(range(10), repeat(arr.name))))
    arr.close()
    arr.unlink()
    return result

if __name__ == '__main__':
    for i in range(100):
        print(f"第{i+1}次执行结果: {meta_fun()}")

这个方案最简单,但缺点是每次都要创建销毁进程,性能会打折扣。

总结

Windows下Python的shared_memory确实存在这个设计上的局限性,主要是因为系统机制和模块实现的差异。如果要长期复用进程池,优先推荐方案1(手动调用Windows API);如果内存大小动态变化,方案2的内存池管理更合适;如果性能要求不高,方案3最省心。

内容的提问来源于stack exchange,提问作者Samufi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 05:47:43