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

pybind11中Boost Interprocess共享内存管理器销毁后访问NumPy数组致段错误

共享内存NumPy视图销毁后段错误问题

我正在开发一个项目,使用Boost.Interprocess管理共享内存,通过pybind11将C++类暴露给Python,目标是无需拷贝数据,以视图形式访问共享内存中的NumPy数组。但当共享内存管理器销毁后,访问该数组会触发段错误。

最小复现示例

C++代码

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <string>

namespace py = pybind11;
namespace bip = boost::interprocess;

typedef bip::allocator<float, bip::managed_shared_memory::segment_manager> ShmemAllocator;
typedef bip::vector<float, ShmemAllocator> MyVector;

class SharedMemoryManager {
public:
    SharedMemoryManager(const std::string& name, size_t size) 
        : shm_name(name), shm_size(size), shm(bip::open_or_create, name.c_str(), size) {
        
        allocator = new ShmemAllocator(shm.get_segment_manager());
        vector = shm.find_or_construct<MyVector>("MyVector")(*allocator);
        
        if (vector->empty()) {
            vector->resize(100);
            for (size_t i = 0; i < 100; ++i) {
                (*vector)[i] = static_cast<float>(i);
            }
        }
    }

    ~SharedMemoryManager() {
        delete allocator;
        bip::shared_memory_object::remove(shm_name.c_str());
    }

    py::array_t<float> get_array() {
        return py::array_t<float>(
            {static_cast<ssize_t>(vector->size())},
            {sizeof(float)},
            vector->data(),
            py::capsule(this, [](void *) { /* Empty deleter */ })
        );
    }

private:
    std::string shm_name;
    size_t shm_size;
    bip::managed_shared_memory shm;
    ShmemAllocator* allocator;
    MyVector* vector;
};

PYBIND11_MODULE(segfault_example, m) {
    py::class_<SharedMemoryManager>(m, "SharedMemoryManager")
        .def(py::init<const std::string&, size_t>())
        .def("get_array", &SharedMemoryManager::get_array);
}

Python代码

import segfault_example

manager = segfault_example.SharedMemoryManager("MySharedMemory", 65536)  # 64KB shared memory

arr = manager.get_array()
print("Array before destruction:", arr[:5])  # 正常运行

del manager

# 此处触发段错误
print(arr)

问题说明

执行del manager销毁管理器后,共享内存被释放,后续访问NumPy数组arr会触发段错误,因为其指向的内存已被移除。

需求

在共享内存可用时无需读时拷贝的前提下,实现以下任一方案:

  • 能否在共享内存销毁时让NumPy数组失效?可以跟踪Python中的引用计数,知晓哪些数组被赋值给了变量。
  • 基于引用计数,在共享内存销毁前将数据复制到本地内存,若能修改NumPy数组的指针也可解决问题。
  • 其他可行方案。

已尝试方案

尝试通过全局变量跟踪并重新分配数组的数据指针,但没有效果,推测NumPy可能已解引用指针,或许需要使用更底层的Python调用实现?


解决方案

方案1:让NumPy数组持有共享内存管理器的引用

当前代码中,py::capsule传入管理器指针但deleter为空,导致NumPy数组不持有管理器的引用,管理器被销毁后共享内存直接释放。修改逻辑让NumPy数组关联管理器的生命周期:

  1. 修改get_array方法,让NumPy数组持有管理器的Python对象引用:
py::array_t<float> get_array() {
    return py::array_t<float>(
        {static_cast<ssize_t>(vector->size())},
        {sizeof(float)},
        vector->data(),
        py::cast(this)  // 传入管理器的Python对象,增加引用计数
    );
}
  1. 调整管理器的析构逻辑,将共享内存清理移到Python对象的__del__方法中:
class SharedMemoryManager {
public:
    // ... 其他代码保持不变 ...

    ~SharedMemoryManager() {
        delete allocator;
        // 移除原析构函数中的共享内存清理代码
    }
};

PYBIND11_MODULE(segfault_example, m) {
    py::class_<SharedMemoryManager>(m, "SharedMemoryManager")
        .def(py::init<const std::string&, size_t>())
        .def("get_array", &SharedMemoryManager::get_array)
        .def("__del__", [](SharedMemoryManager& self) {
            // 仅当Python对象被销毁时清理共享内存
            bip::shared_memory_object::remove(self.shm_name.c_str());
        });
}

这样只要NumPy数组还存在,管理器的Python对象就不会被销毁,共享内存也会保持可用;当数组和管理器都被销毁后,才会触发共享内存清理。

方案2:共享内存销毁时自动拷贝数据到本地

如果需要在管理器销毁时自动将数组数据拷贝到本地,可以通过Python C API修改NumPy数组的底层指针:

  1. 在SharedMemoryManager中添加数组跟踪逻辑:
#include <vector>
#include <mutex>

class SharedMemoryManager {
public:
    // ... 其他代码保持不变 ...

    py::array_t<float> get_array() {
        auto arr = py::array_t<float>(
            {static_cast<ssize_t>(vector->size())},
            {sizeof(float)},
            vector->data()
        );
        // 跟踪数组的底层指针
        std::lock_guard<std::mutex> lock(mtx);
        tracked_arrays.push_back(arr.ptr());
        return arr;
    }

private:
    // ... 原有成员变量 ...
    std::vector<PyObject*> tracked_arrays;
    std::mutex mtx;

    ~SharedMemoryManager() {
        delete allocator;
        // 拷贝所有跟踪数组的数据到本地
        std::lock_guard<std::mutex> lock(mtx);
        for (PyObject* arr_obj : tracked_arrays) {
            PyArrayObject* arr = reinterpret_cast<PyArrayObject*>(arr_obj);
            if (!arr || PyArray_DATA(arr) != vector->data()) continue;

            // 创建本地拷贝
            npy_intp shape[] = {static_cast<npy_intp>(vector->size())};
            PyArrayObject* local_copy = reinterpret_cast<PyArrayObject*>(
                PyArray_SimpleNew(1, shape, NPY_FLOAT)
            );
            std::memcpy(PyArray_DATA(local_copy), PyArray_DATA(arr), PyArray_NBYTES(arr));
            
            // 将原数组的底层数据替换为本地拷贝
            PyArray_SetBaseObject(arr, reinterpret_cast<PyObject*>(local_copy));
        }
        bip::shared_memory_object::remove(shm_name.c_str());
    }
};

此方案会在管理器销毁时,自动将所有关联的共享内存数组替换为本地拷贝,后续访问数组不会触发段错误,但会产生一次数据拷贝。

方案3:手动控制共享内存生命周期

取消管理器自动清理共享内存的逻辑,改为手动调用清理方法:

  1. 修改C++代码的析构和清理逻辑:
class SharedMemoryManager {
public:
    // ... 其他代码保持不变 ...

    ~SharedMemoryManager() {
        delete allocator;
        // 不自动清理共享内存
    }

    static void cleanup(const std::string& name) {
        bip::shared_memory_object::remove(name.c_str());
    }
};

PYBIND11_MODULE(segfault_example, m) {
    py::class_<SharedMemoryManager>(m, "SharedMemoryManager")
        .def(py::init<const std::string&, size_t>())
        .def("get_array", &SharedMemoryManager::get_array);
    m.def("cleanup_shared_memory", &SharedMemoryManager::cleanup);
}
  1. 在Python中手动控制清理时机:
import segfault_example

manager = segfault_example.SharedMemoryManager("MySharedMemory", 65536)
arr = manager.get_array()
print("Array before destruction:", arr[:5])

del manager
# 此时共享内存仍存在,访问arr正常
print(arr)

# 确认不再需要时手动清理
segfault_example.cleanup_shared_memory("MySharedMemory")

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 11:34:55