C++原子操作实现shared_ptr/weak_ptr的内存顺序与线程栅栏疑问
学习std::atomic与std::memory_order后,我参考微软博客《Inside STL: Smart pointers》尝试实现线程安全的shared_ptr<T>与weak_ptr<T>,但对以下内容存在疑问:
实现代码
控制块
class control_block { public: constexpr control_block() : strong(1), weak(1) {} void inc_strong() { strong.fetch_add(1, std::memory_order_relaxed); } void inc_weak() { weak.fetch_add(1, std::memory_order_relaxed); } int32_t dec_strong() { return strong.fetch_sub(1, std::memory_order_release); } int32_t dec_weak() { return weak.fetch_sub(1, std::memory_order_release); } int32_t use_count() { return strong.load(std::memory_order_relaxed); } public: std::atomic<int32_t> strong; std::atomic<int32_t> weak; };
shared_ptr<T>实现
template<class T> class shared_ptr { public: using element_type = T; using weak_type = weak_ptr<T>; friend class weak_ptr<T>; public: constexpr shared_ptr() noexcept: object(nullptr), blk(nullptr) {} explicit shared_ptr(T *ptr) : object(ptr), blk(new control_block()) {} shared_ptr(const shared_ptr &other) noexcept { object = other.object; blk = other.blk; if (blk) { blk->inc_strong(); } } shared_ptr(shared_ptr &&other) noexcept { object = other.object; blk = other.blk; other.object = nullptr; other.blk = nullptr; } shared_ptr<T> &operator=(const shared_ptr &other) noexcept { if (this == &other) return *this; release_strong_ref(); object = other.object; blk = other.blk; if (blk) { blk->inc_strong(); } return *this; }; shared_ptr<T> &operator=(shared_ptr &&other) noexcept { if (this == &other) return *this; object = other.object; blk = other.blk; other.object = nullptr; other.blk = nullptr; return *this; }; ~shared_ptr() { release_strong_ref(); } public: explicit operator bool() const noexcept { return blk != nullptr; } [[nodiscard]] element_type *get() const noexcept { return object; } [[nodiscard]] int32_t use_count() const noexcept { return blk ? blk->use_count() : 0; } T &operator*() const noexcept { return *object; } T *operator->() const noexcept { return object; } private: void release_strong_ref() noexcept { if (!blk) return; if (blk->dec_strong() == 1) { std::atomic_thread_fence(std::memory_order_acquire); delete object; if (blk->dec_weak() == 1) { std::atomic_thread_fence(std::memory_order_acquire); delete blk; } } } private: T *object; control_block *blk; };
weak_ptr<T>实现
template<class T> class weak_ptr { public: using element_type = T; friend class shared_ptr<T>; public: constexpr weak_ptr() noexcept: object(nullptr), blk(nullptr) {}; weak_ptr(const weak_ptr &other) noexcept { release_weak_ref(); object = other.object; blk = other.blk; if (blk) { blk->inc_weak(); } } weak_ptr(weak_ptr &&other) noexcept { object = other.object; blk = other.blk; other.object = nullptr; other.blk = nullptr; } template<class Y> weak_ptr(const shared_ptr<Y> &other) noexcept { if (!other) { object = nullptr; blk = nullptr; return; } object = other.object; blk = other.blk; blk->inc_weak(); } ~weak_ptr() { release_weak_ref(); } template<class Y> weak_ptr<T> &operator=(const shared_ptr<Y> &other) noexcept { release_weak_ref(); if (!other) { object = nullptr; blk = nullptr; return *this; } object = other.object; blk = other.blk; blk->inc_weak(); return *this; }; weak_ptr<T> &operator=(const weak_ptr &other) noexcept { if (this == &other) return *this; release_weak_ref(); object = other.object; blk = other.blk; if (blk) { blk->inc_weak(); } return *this; }; weak_ptr<T> &operator=(weak_ptr &&other) noexcept { if (this == &other) return *this; object = other.object; blk = other.blk; other.object = nullptr; other.blk = nullptr; return *this; }; public: explicit operator bool() const noexcept { return blk != nullptr; } shared_ptr<T> lock() noexcept { if (!blk) return shared_ptr<T>(); int32_t old = blk->strong.load(std::memory_order_relaxed); while (old > 0) { if (blk->strong.compare_exchange_weak(old, old + 1, std::memory_order_acquire)) { shared_ptr<T> ptr; ptr.object = object; ptr.blk = blk; return ptr; } old = blk->strong.load(std::memory_order_relaxed); } return shared_ptr<T>(); } [[nodiscard]]int32_t use_count() const noexcept { return blk ? blk->use_count() : 0; } [[nodiscard]]bool expired() const noexcept { return use_count() == 0; } private: void release_weak_ref() noexcept { if (!blk) return; if (blk->dec_weak() == 1) { std::atomic_thread_fence(std::memory_order_acquire); delete blk; } } private: T *object; control_block *blk; };
疑问解答
1. lock()中CAS循环的内存顺序
- 成功时:使用
std::memory_order_acquire。CAS成功递增强引用后,需要确保后续对object的访问能看到其他线程对该对象的所有修改——其他线程释放强引用时的release操作会同步这些修改,acquire语义会阻止后续内存操作重排到CAS之前,同时保证可见性。 - 失败时:使用
std::memory_order_relaxed即可。失败仅需重新加载强引用的最新值,无需同步语义,因为CAS失败意味着值已被其他线程修改,我们只需要获取新值重试,不需要建立内存可见性。
优化后的CAS调用应为:
if (blk->strong.compare_exchange_weak(old, old + 1, std::memory_order_acquire, std::memory_order_relaxed))
2. 析构中std::atomic_thread_fence的内存顺序是否正确
是正确的,理由如下:
- 当
dec_strong()返回1时,当前线程是最后一个持有强引用的线程。dec_strong()用memory_order_release会同步当前线程对object的所有写入;后续的acquire栅栏会阻止delete object重排到栅栏之前,同时保证能看到其他线程对object的最终写入,确保安全删除。 - 同理,
dec_weak()返回1时,当前线程是最后一个持有弱引用的线程,acquire栅栏确保控制块的所有操作完成后再删除控制块,避免悬空指针。
3. 跨平台线程安全测试方法
仅在x86平台测试无法暴露弱内存模型下的问题,可通过以下方式全面验证:
- 弱内存模型平台验证:使用ARM、PowerPC等弱内存模型硬件,或用QEMU模拟这类平台运行测试程序,触发内存重排相关问题。
- 压力测试:编写多线程用例,让大量线程同时执行
shared_ptr的创建、拷贝、移动、析构,以及weak_ptr的lock、析构等操作,长时间运行后检查崩溃、内存泄漏或数据损坏情况。 - 工具检测:使用ThreadSanitizer(TSAN)这类线程安全检测工具,它能自动检测数据竞争、内存顺序错误,支持GCC、Clang等编译器,无需依赖特定硬件。
- 边界场景测试:构造极端场景验证:
- 最后一个强引用释放时,多个
weak_ptr并发调用lock; - 多线程同时拷贝同一个
shared_ptr; weak_ptr在关联对象销毁后尝试lock;- 频繁创建销毁智能指针,验证引用计数的正确性。
- 最后一个强引用释放时,多个
内容的提问来源于stack exchange,提问作者h243jian
相关产品推荐
相关产品推荐

