c++oding="utf-8" ?>
std::atomic不能直接封装非平凡可复制对象实现无锁单例,因标准要求T必须trivially copyable且多数编译器对非POD类型退化为加锁;正确做法是用std::atomic或C++20起保证lock-free的std::atomic,并配合CAS循环与acquire/release内存序确保构造完成后再可见。

直接说结论:std::atomic<:shared_ptr>> 或 std::atomic 配合 memory_order_acquire/memory_order_release 可以实现线程安全的无锁单例,但**不能用 std::atomic 直接封装完整对象(除非 T 是平凡可复制且满足 lock-free 要求)** —— 这是初学者最容易栽跟头的地方。
为什么不能直接用 std::atomic?
C++ 标准要求 std::atomic 的 T 必须是 trivially copyable,且多数编译器对非 POD 类型会退化为内部加锁实现(即不是真正无锁)。哪怕 MySingleton 只有 private 构造函数 + static instance(),一旦你写成:
std::atomic<mysingleton> s_instance; // ❌ 编译可能通过,但 runtime 可能 fallback 到 mutex</mysingleton>
你就已经失去了“无锁”意义。实测在 GCC/Clang 下,std::atomic<:string> 或含虚函数、自定义构造/析构的类,is_lock_free() 返回 false。
可行路径只有两条:
- 用
std::atomic手动管理原始指针(需搭配 placement new 和显式析构) - 用
std::atomic<:shared_ptr>>(C++20 起保证 lock-free;C++11/14/17 中取决于实现,但主流 libstdc++/libc++ 通常 lock-free)
std::atomic<:shared_ptr>> 单例的正确初始化顺序
关键不在“原子赋值”,而在“确保对象构造完成后再让其他线程可见”。错误写法:
static std::shared_ptr<mysingleton> instance() {
static std::atomic<:shared_ptr>> s_ptr{nullptr};
auto ptr = s_ptr.load(std::memory_order_acquire);
if (!ptr) {
ptr = std::make_shared<mysingleton>(); // ❌ 构造发生在此处,但 store 之前可能被重排
s_ptr.store(ptr, std::memory_order_release); // store 在构造之后,但无同步保障
}
return ptr;
}</mysingleton></:shared_ptr></mysingleton>
问题在于:编译器或 CPU 可能把 make_shared 内部的内存分配、构造、引用计数初始化等步骤重排,导致其他线程看到未完全构造好的对象。
正确做法是用 CAS 循环 + acquire/release 配对:
- 先用
std::make_shared构造对象(这步本身线程安全) - 用
compare_exchange_weak原子地把空指针替换成新对象指针 - CAS 成功者负责构造;失败者直接返回已存在的指针
- store 使用
memory_order_release,load 使用memory_order_acquire,形成同步点
避免 ABA 问题与内存泄漏的细节
无锁单例不涉及链表指针反复复用,所以 ABA 不是主要风险,但以下两点极易被忽略:
-
std::shared_ptr的引用计数更新本身是原子的,但它的operator=赋值不是原子操作 —— 必须用load()/store()显式控制 - 如果单例对象持有资源(如文件句柄、线程),且程序需要显式销毁(而非依赖静态析构),那么
std::shared_ptr的自动析构时机不可控;此时应改用std::atomic+ 手动delete,并在atexit或 RAII wrapper 中确保只销毁一次 - 所有 load/store 必须使用明确的 memory order;默认
std::memory_order_seq_cst安全但慢,单例场景用acquire/release足够
一个工业级可用的 minimal 实现
以下代码在 GCC 12+ / Clang 14+ / libc++ 15+ 上实测 lock-free(is_lock_free() == true):
class MySingleton {
public:
static std::shared_ptr<mysingleton> instance() {
static std::atomic<:shared_ptr>> s_ptr{nullptr};
std::shared_ptr<mysingleton> expected = nullptr;
auto desired = std::make_shared<mysingleton>();
while (!s_ptr.compare_exchange_weak(expected, desired,
std::memory_order_release,
std::memory_order_acquire)) {
if (expected) return expected; // 已存在,直接返回
desired = std::make_shared<mysingleton>(); // 理论上不会到这,但防异常
}
return desired;
}
private:
MySingleton() = default;
};</mysingleton></mysingleton></mysingleton></:shared_ptr></mysingleton>
注意:这个版本不处理异常(make_shared 抛异常时可能多次构造),生产环境建议在外层加 std::call_once 做兜底,或改用 Meyers 单例 + atomic flag 标记初始化状态 —— 无锁和绝对异常安全很难兼得,得按实际场景权衡。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











