C++11起可用std::atomic实现安全的双重检查锁定变体,关键在于用memory_order_acquire/release配对确保指针发布与对象构造顺序,而非单纯无锁;推荐优先使用std::call_once以保障异常安全与可维护性。

std::atomic 实现双重检查锁定的无锁变体
标准 double-checked locking(DCL)在 C++11 之前不安全,根本原因是编译器重排和 CPU 乱序执行可能导致 instance 指针被提前写入但对象构造未完成。C++11 起,用 std::atomic<t></t> 配合恰当内存序可规避该问题,本质是把“指针发布”变成原子且带同步语义的操作。
关键不是“去掉锁”,而是把锁从临界区挪到初始化阶段——首次调用仍需同步,但后续读取完全无锁、无分支预测失败开销。
-
std::atomic<t> instance{nullptr}</t>必须声明为静态存储期,且不能是局部静态变量(否则退化为标准线程安全单例,不满足“无锁读”诉求) - 必须使用
memory_order_acquire读 +memory_order_release写配对,memory_order_relaxed不足以防止构造重排 - 禁止直接用
new T()构造后赋值;必须确保构造完成后再原子发布指针,推荐 placement new + 手动调用构造函数,或依赖new的强保证(C++11 起new表达式本身已含memory_order_seq_cst语义)
为什么不能只用 memory_order_relaxed
若读写都用 memory_order_relaxed,编译器和 CPU 均可能将对象成员的初始化指令重排到指针赋值之后。线程 A 看到非空 instance,但访问其成员时触发未定义行为(UB),典型表现为段错误或随机值。
示例中易被忽略的陷阱:
- 误写成
if (instance.load(std::memory_order_relaxed) != nullptr) return instance.load(std::memory_order_relaxed);—— 两次 load 可能返回不同值(即使没其他线程写),因 relaxed 不保证单拷贝原子性(single-copy atomicity)在所有平台都成立 - 在 store 前漏掉
std::atomic_thread_fence(std::memory_order_release)或等效屏障,尤其当构造体含非原子成员初始化时 - 把
instance.store(ptr, std::memory_order_relaxed)当作“发布完成”信号 —— 它只保证指针写入原子,不保证之前所有写对其他线程可见
std::atomic_flag 实现纯无锁初始化标志位
若单例构造极轻量(如仅初始化几个整数),可用 std::atomic_flag 替代 mutex 控制初始化入口。它是最底层的无锁原语,保证 lock-free,且尺寸最小(通常单字节)。
注意点:
-
std::atomic_flag对象必须用ATOMIC_FLAG_INIT初始化,不能用 = {} 或默认构造 - 它不提供 load/store 接口,只有
test_and_set()和clear();典型模式是自旋等待flag.test_and_set(std::memory_order_acquire)返回 false - 初始化完成后必须调用
flag.clear(std::memory_order_release),否则其他线程永远无法进入 —— 这个 release 是为了同步初始化结果 - 不能单独靠
atomic_flag保证对象构造完成,仍需搭配memory_order_releasestore 到 instance 指针,或用 fence
实际工程中更推荐 std::call_once + std::once_flag
虽然不符合“纯原子操作”的技术洁癖,但 std::call_once 是标准库提供的、经过充分验证的线程安全初始化机制。它内部使用平台最优原语(Linux 下是 futex,Windows 下是 SRWLock),性能与手写原子方案差距极小,且彻底规避 ABA、内存泄漏、构造异常导致的 flag 残留等问题。
除非你明确需要:(1)初始化后 100% 无分支、零原子操作开销的读路径;(2)运行时动态切换单例实例;(3)嵌入式环境禁用部分标准库 —— 否则,手写原子单例带来的维护成本和隐蔽 bug 风险远高于收益。
真正容易被忽略的是异常安全:如果单例构造函数抛异常,std::call_once 会自动重置状态,而手写原子方案必须显式捕获并 clear() flag、store(nullptr),否则后续调用永久阻塞或崩溃。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











