登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  python教程

Python shared_memory.SharedMemoryManager 怎样批量清理共享段

来源:17golang原创

时间:2026-11-14 06:21:28 377浏览 收藏

如果一个任务同时创建了多个共享内存段,真正麻烦的往往不是“怎么创建”,而是结束时记住每个对象的名字、句柄和释放顺序。Python 的 SharedMemoryManager 适合把这部分责任收拢起来:由管理器创建的 SharedMemory 和 ShareableList 会被纳入同一个生命周期,退出时调用 shutdown() 就能批量触发释放。

官方文档:https://docs.python.org/3/library/multiprocessing.shared_memory.html

批量清理的关键不是给每个共享段写一套重复的 unlink(),而是让所有共享对象都从同一个 SharedMemoryManager 创建,并把管理器放进 with 或明确的 try/finally 边界。

先分清 close、unlink 和 shutdown

共享内存清理容易混淆,是因为“关闭当前进程的访问句柄”和“删除底层共享段”是两件事。SharedMemory.close() 只关闭当前实例持有的文件描述符或句柄;当共享段已经不再被任何进程使用时,才由 unlink() 删除底层对象。不同进程可能各自持有同一段的句柄,所以不能把一次 close() 理解成全局删除。

SharedMemoryManager.shutdown() 的职责更高一层:它会对通过该管理器创建的共享内存对象触发 unlink(),然后停止管理器进程。也就是说,文章标题里的“批量”来自管理器保存的对象集合,而不是调用方自己维护一个共享段列表。

SharedMemoryManager、管理器进程、共享段和 ShareableList 的静态生命周期关系说明图
图1:说明图展示管理器、共享对象和调用方之间的静态关系,不是运行截图或执行证据。

让所有共享对象由同一个管理器创建

需要批量清理时,创建入口要保持一致。下面同时创建一个固定长度的 ShareableList 和一个原始 SharedMemory 段;它们都挂在同一个管理器下面,后续只需要处理管理器的生命周期。

from multiprocessing.managers import SharedMemoryManager


def create_shared_objects():
    # start() 会启动负责管理共享对象生命周期的服务进程。
    manager = SharedMemoryManager()
    manager.start()

    try:
        # 通过管理器创建的对象会进入同一个待清理集合。
        values = manager.ShareableList([0, 0, 0, 0])
        raw_block = manager.SharedMemory(size=128)
        return manager, values, raw_block
    except Exception:
        # 创建中途失败时,确保已经创建的对象不会遗留。
        manager.shutdown()
        raise

这里的关键不在变量名,而在创建路径:如果改成直接调用 SharedMemory(create=True, ...),那个对象就不再自动属于这个管理器,调用方必须自行保存句柄并规划 close() 与 unlink()。

用 with 语句覆盖正常退出和异常退出

更适合业务代码的写法是使用上下文管理器。进入 with 时启动管理器,离开代码块时调用 shutdown();无论是正常走到末尾,还是代码块中抛出了异常,清理边界都集中在同一个位置。

from multiprocessing import Process
from multiprocessing.managers import SharedMemoryManager


def fill_range(shared_values, start, end):
    # 子进程只写自己的区间,避免多个进程同时改同一个槽位。
    for index in range(start, end):
        shared_values[index] = index * index


def run_job():
    # 离开 with 时由管理器统一释放内部创建的共享对象。
    with SharedMemoryManager() as manager:
        shared_values = manager.ShareableList([0] * 8)
        workers = [
            Process(target=fill_range, args=(shared_values, 0, 4)),
            Process(target=fill_range, args=(shared_values, 4, 8)),
        ]

        for worker in workers:
            # 先启动所有工作进程,再等待它们访问共享列表。
            worker.start()
        for worker in workers:
            worker.join()

        # 子进程都结束后再读取结果,避免清理边界与访问边界重叠。
        if any(worker.exitcode != 0 for worker in workers):
            raise RuntimeError("worker exited with an error")
        return list(shared_values)


if __name__ == "__main__":
    print(run_job())

这个示例的返回值只是业务结果,清理动作由 with 的退出逻辑负责。不要在 with 内部提前对管理器创建的共享段调用 unlink(),否则后续仍持有的引用可能访问到已被删除的底层对象。

应用作用域、SharedMemory 句柄、共享内存块与管理器清理边界的静态关系说明图
图2:结构图区分访问句柄、底层资源和统一清理入口,帮助判断异常退出时由谁负责释放。

多个进程时,先收拢访问再关闭管理器

管理器可以批量释放资源,但它不会替调用方等待业务进程完成。实际工程里应先让工作进程结束,再离开 with 或显式调用 shutdown()。否则管理器已经删除共享段,而工作进程还在读写,就会把资源释放问题变成并发时序问题。

如果工作进程可能卡住,可以把等待和兜底终止写在清理逻辑里,但终止进程仍然是业务层的职责。管理器负责共享段生命周期,进程编排负责保证没有活跃访问者,两个职责不要混在一个“清理函数”里。

def wait_workers(workers, timeout):
    # 先给正常退出留出时间,避免无条件终止正在写共享数据的进程。
    for worker in workers:
        worker.join(timeout)

    for worker in workers:
        if worker.is_alive():
            # 只有超时仍未退出时才做进程级兜底。
            worker.terminate()
            worker.join()

独立 SharedMemory 连接不能混用管理器语义

有些场景是一个进程创建共享段,另一个独立启动的进程根据名称附加。此时附加进程拿到的只是一个访问句柄,并不自然拥有创建者的管理器上下文。附加方通常在不用时调用 close(),由约定的拥有者决定何时调用一次 unlink()。

Python 文档还提醒,非共同祖先进程各自使用资源跟踪器时,track=True 可能让第一个退出的进程触发删除;如果已经有其他进程负责记账,独立进程需要结合实际拓扑考虑 track=False。Windows 下该参数会被忽略,底层共享内存会在所有句柄关闭后由系统处理。

因此可以按下面的职责表做选择:

场景创建方式结束时的主要动作
同一任务内批量创建SharedMemoryManager等待工作进程后退出 with 或调用 shutdown()
独立进程附加SharedMemory(name=...)附加方 close(),拥有者按协议调用一次 unlink()
创建中途异常管理器已启动但对象未全部创建在 except 中调用 manager.shutdown() 后继续抛出异常

一份可复用的清理判断

把问题压缩成三个判断就够了:共享对象是不是由同一个管理器创建的;还有没有进程持有活跃访问;当前代码是否处在 with 或 finally 的清理边界。如果三个答案分别是“是、没有、是”,优先使用管理器的统一退出;如果共享段来自外部名称附加,则回到拥有者协议,不要把 shutdown() 当成全局回收按钮。

这套写法解决的是资源归属与退出路径,不会自动解决共享数据的并发写冲突。共享列表的槽位分配、锁、进程异常传播仍需要在业务层设计。

常见问题

SharedMemoryManager.shutdown() 会关闭所有进程的句柄吗?

它负责释放由该管理器管理的共享对象并停止管理器进程;其他进程仍应在自己的访问结束后关闭本地句柄。

只调用 close() 能删除共享内存段吗?

不能把两者等同。close() 关闭当前实例的访问,底层对象是否删除还取决于 unlink() 或平台的生命周期规则。

为什么推荐 with SharedMemoryManager()?

因为它把正常退出和异常退出都纳入同一个上下文边界,减少漏写 shutdown() 和遗留共享段的机会。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>