c++oding="utf-8" ?>
std::shared_ptr本身不完全线程安全:引用计数操作原子,但对象访问和指针值读写(C++17前)非原子,需用std::atomic保证指针值交换安全;真正风险在于生命周期管理与数据竞争的耦合。

std::shared_ptr 本身线程安全吗?
不完全安全。std::shared_ptr 的引用计数操作(增减)是原子的,但对象本身的访问不是。也就是说:多个线程可以同时拷贝或析构同一个 std::shared_ptr 实例(比如共享一个 std::shared_ptr 变量),引用计数不会错;但若多个线程通过不同拷贝去读写它指向的 T 对象,就必须额外加锁。
常见误用场景:
- 把一个
std::shared_ptr对象(非 const)暴露给多个线程,且线程直接调用p->mutate()—— 这里p指向的T是共享可变状态,std::shared_ptr不管这个 - 在多线程中反复赋值同一个
std::shared_ptr变量(如全局变量),虽然引用计数安全,但指针值本身读写非原子,可能读到中间态(C++17 起保证std::shared_ptr的赋值/读取是原子的,但旧标准需用std::atomic<:shared_ptr>>)
什么时候必须用 std::atomic<:shared_ptr>>?
当你需要在线程间**安全地交换、发布或读取 shared_ptr 的指针值本身**时,比如实现无锁发布、双检查锁定缓存、或生产者-消费者传递指针。普通 std::shared_ptr 的拷贝/赋值在 C++17 前不是原子操作,直接读写同一变量会导致数据竞争。
实操建议:
- C++17 及以后:对同一
std::shared_ptr变量的读写仍推荐用std::atomic<:shared_ptr>>,因为标准只保证“单个操作原子”,不保证复合操作(如 compare_exchange_weak) - 初始化必须用
ATOMIC_VAR_INIT(C++17 已弃用)或直接构造:std::atomic<:shared_ptr>> ptr{std::make_shared(42)}; - 不能直接用
=赋值,要用store()/load()/exchange()或compare_exchange_weak()
示例(安全发布):
std::atomic<:shared_ptr>> g_cache{nullptr};
void publish_data() {
auto new_data = std::make_shared<data>();
// ... 初始化 new_data
g_cache.store(new_data, std::memory_order_release);
}
std::shared_ptr<data> get_cached_data() {
return g_cache.load(std::memory_order_acquire);
}
</data></data></:shared_ptr>
自己实现线程安全引用计数器?别这么做
除非你在写标准库或嵌入式受限环境,否则不要手写引用计数逻辑。原因很实在:
-
std::shared_ptr的控制块(control block)已用原子操作(std::atomic_int或平台级原子指令)实现计数增减,且做了内存序优化(如fetch_add+memory_order_relaxed) - 手动实现容易漏掉 weak count、自定义删除器、分配器适配等边界情况
- 性能上很难超越 libstdc++/libc++ 的内联汇编或
__atomic内建函数实现 - 调试困难:竞态一旦发生,往往表现为偶发崩溃或计数错乱,难以复现
如果真有特殊需求(如计数溢出检测、日志埋点),可继承 std::shared_ptr 并包装关键操作,但控制块本身不可替换。
真正要注意的线程安全陷阱在哪?
不在引用计数,而在对象生命周期与数据竞争的耦合。最容易被忽略的是:引用计数归零触发析构,但析构函数本身可能访问其他仍在使用的共享资源(比如回调中又用了已被销毁对象的 std::shared_ptr)。
典型问题:
- 循环引用导致内存泄漏(
std::shared_ptr+std::shared_ptr互相持有)→ 改用std::weak_ptr打破循环 - 析构时调用异步回调,而回调对象已销毁 → 用
std::weak_ptr在回调前lock()检查有效性 - 多个线程同时调用
std::shared_ptr::reset(),虽计数安全,但若 reset 后立即 new 对象,可能造成短暂空窗期被其他线程读到空指针
所以,线程安全的重心从来不是“怎么让计数更原子”,而是“怎么让对象的使用逻辑与生命周期解耦”。std::weak_ptr::lock() 就是为此设计的:它不延长生命周期,只尝试获取一个临时的、有效的 std::shared_ptr。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











