std::atomic 在简单变量(如 int、bool)的单次读写或原子运算(如 fetch_add)且无需多变量协同时比 std::mutex 快,因其避免系统调用和上下文切换,常编译为单条 CPU 原子指令;但复杂类型或错误使用 memory_order 会丧失优势甚至引发未定义行为。

atomic 在什么场景下比 mutex 快?
当操作是简单读写(如 int、bool、指针)且不涉及多变量协同时,std::atomic 通常比 std::mutex 快得多——它避免了系统调用和上下文切换开销,底层常映射为单条 CPU 指令(如 x86 的 lock xadd)。
但要注意:不是所有 atomic 操作都“轻量”。std::atomic<:shared_ptr>> 或自定义类型(需满足 trivially copyable 且大小 ≤ 指令原子宽度)的 load/store 可能退化为内部锁实现,性能反而不如显式 mutex。
-
std::atomic的fetch_add是典型高性能场景; - 若需同时更新两个关联字段(如
count和sum),atomic无法保证原子性,硬上会引发数据竞争; -
memory_order_relaxed能进一步提速,但仅适用于计数器、标志位等无需同步顺序的场合;用错memory_order(如该用acq_rel却用了relaxed)会导致未定义行为,且难以复现。
mutex 何时不可替代?
任何需要保护一段逻辑(而非单个变量)、或涉及 I/O、内存分配、异常抛出、非平凡构造/析构的操作,std::mutex(或更细粒度的 std::shared_mutex)仍是唯一选择。
比如在容器中插入元素:std::vector::push_back 可能触发重分配,这绝非原子操作;又比如日志写入函数需格式化字符串再写文件,中间步骤无法拆解为原子指令。
立即学习“C++免费学习笔记(深入)”;
- 频繁争用同一把
std::mutex会显著拖慢吞吐(线程排队、唤醒开销); -
std::recursive_mutex容易掩盖设计缺陷,应优先重构为无重入逻辑; -
std::timed_mutex或try_lock可防死锁,但轮询try_lock会空转 CPU,慎用于高并发热路径。
无锁结构(lock-free)真的更快吗?
“无锁”不等于“无开销”,而是指没有线程因等待锁而被阻塞。但实际性能取决于具体实现与硬件支持。例如 boost::lockfree::queue 在低争用下表现优异,但在高争用下可能因大量 CAS 失败导致缓存行乒乓(cache line bouncing),反不如带自旋优化的 std::mutex。
更关键的是:无锁编程极易出错。一个典型的错误是 ABA 问题——某值从 A→B→A,CAS 误判为未变。标准库中只有 std::atomic 提供基础支持,但需手动管理版本号或使用 std::atomic<:shared_ptr> 配合引用计数规避。
- c++20 引入
std::atomic_ref,允许对已有对象(如数组元素)做原子操作,减少拷贝,但要求对象生命周期严格可控; - 绝大多数业务代码不需要手写无锁结构,
abseil或Folly中的成熟实现更值得信赖; - 用
std::atomic实现自旋锁 ≠ 无锁结构——它仍是阻塞式等待,且在核数少、负载高时效率极差。
如何实测三者真实开销?
别依赖理论模型。用 std::chrono::high_resolution_clock 测端到端耗时,更要关注 perf stat -e cache-misses,context-switches,instructions,cycles 这类指标。尤其注意:
- 测试必须在多核满载下运行(
taskset -c 0,1,2,3 ./bench),单核结果毫无意义; - 避免编译器优化掉“无用”操作:对结果变量用
volatile或asm volatile("" ::: "memory")内存栅栏; - 对比时统一使用相同内存布局(如将热点变量对齐到独立 cache line),否则
atomic的 false sharing 会让结果失真。
#include #include #include #include alignas(64) std::atomic atomic_counter{0}; alignas(64) int raw_counter = 0; alignas(64) std::mutex mtx; int mutex_counter = 0; void inc_atomic() { for (int i = 0; i < 100000; ++i) atomic_counter.fetch_add(1, std::memory_order_relaxed); } void inc_mutex() { for (int i = 0; i < 100000; ++i) { std::lock_guard lk(mtx); ++mutex_counter; } } // 注意:raw_counter 直接 ++ 是未定义行为,仅作对比基线(必错)
真正难的从来不是选 atomic 还是 mutex,而是判断哪些操作必须原子、哪些可以放松、哪些根本不需要同步——这得靠对数据流和线程边界的清醒认知,而不是套模板。