如何以线程安全的方式在std::vector中存储std::shared_ptr?
多线程环境下固定大小对象池的轻量无锁实现方案
问题背景
多个线程共享固定大小对象池(初始已知最大容量),部分线程需移除对象,要求对象在无任何线程使用时自动释放。原方案用std::vector<std::shared_ptr<T>>存储,移除时置空指针,但std::atomic<std::shared_ptr<T>>无拷贝构造函数无法直接存入std::vector,只能采用嵌套指针的复杂结构:
struct T {}; std::vector<std::unique_ptr<std::atomic<std::shared_ptr<T>>>> pool;
该方案引入额外内存分配与间接层,代码可读性差,需寻找更简洁、接近无锁的可靠实现。
推荐方案
方案1:用std::array直接存储std::atomic<std::shared_ptr<T>>
由于对象池大小固定,std::array支持聚合初始化,无需拷贝构造元素,可直接存储std::atomic<std::shared_ptr<T>>,完全消除额外间接层:
struct T {}; constexpr size_t MAX_POOL_SIZE = 100; std::array<std::atomic<std::shared_ptr<T>>, MAX_POOL_SIZE> pool; // 初始化池 void init_pool() { for (size_t i = 0; i < MAX_POOL_SIZE; ++i) { pool[i].store(std::make_shared<T>(), std::memory_order_relaxed); } } // 原子置空标记对象为移除 void remove_object(size_t idx) { pool[idx].store(nullptr, std::memory_order_release); } // 原子加载获取对象 std::shared_ptr<T> get_object(size_t idx) { return pool[idx].load(std::memory_order_acquire); }
该方案复用标准库组件,代码简洁,完全贴合固定大小池的需求。
方案2:原子引用计数+对象数组(适配多态场景)
若需支持多态类层次,可拆分对象存储与原子引用计数,通过自定义智能指针管理生命周期:
struct Base { virtual ~Base() = default; }; struct Derived : Base {}; constexpr size_t MAX_POOL_SIZE = 100; std::array<std::unique_ptr<Base>, MAX_POOL_SIZE> objects; std::array<std::atomic<int>, MAX_POOL_SIZE> ref_counts; // 初始化池:每个对象初始持有1个池内引用 void init_pool() { for (size_t i = 0; i < MAX_POOL_SIZE; ++i) { objects[i] = std::make_unique<Derived>(); ref_counts[i].store(1, std::memory_order_relaxed); } } // 获取对象:原子递增引用计数,仅当对象未释放时返回 std::unique_ptr<Base, void(*)(Base*)> get_object(size_t idx) { int old_count = ref_counts[idx].load(std::memory_order_acquire); while (old_count > 0) { if (ref_counts[idx].compare_exchange_weak(old_count, old_count + 1, std::memory_order_acq_rel)) { // 自定义删除器:释放时原子递减计数,计数为0则销毁对象 return {objects[idx].get(), [idx](Base* ptr) { if (ref_counts[idx].fetch_sub(1, std::memory_order_acq_rel) == 1) { objects[idx].reset(); } }}; } } return nullptr; // 对象已被移除 } // 移除对象:池放弃初始引用 void remove_object(size_t idx) { if (ref_counts[idx].fetch_sub(1, std::memory_order_acq_rel) == 1) { objects[idx].reset(); } }
此方案既支持多态,又通过原子操作实现接近无锁的访问,避免了std::atomic<std::shared_ptr>的容器限制。
方案3:第三方无锁容器(如Boost.Lockfree)
若允许引入第三方库,boost::lockfree::queue可直接存储std::shared_ptr<T>,适合动态取出/放回的场景,但对于固定大小的标记式池,前两种方案更适配。
内容的提问来源于stack exchange,提问作者Damir Tenishev
相关产品推荐
相关产品推荐

