跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

C++ std::atomic::exchange用法详解 _ 原子交换操作实现锁【详解】

std::atomic::exchange 是原子RMW操作,而 std::exchange 对 atomic 类型是非原子的 load+store;前者返回旧值并保证原子性,后者仅调用 operator= 和 load(),行为完全不同。 std::atomic::exchange 是无条件原子交换,不是锁,但能用来实现锁逻辑;它不阻塞线程,也不需要互斥量,但必须配合正确的内存序和使用模式。 std::atomic::exchange 为什么不能当 std::exchange 用? 很多人一看到名字里有 “exchange”,就直接写
std::exchange(atomic_var, new_val)
,结果代码在多线程下出错。这不是函数用错了,是根本没调对函数:
std::exchange
是
里的泛型函数,对
std::atomic
类型只是调用其
operator=
和
load()
—— 这两步是非原子的,可能被其他线程打断;
std::atomic::exchange()
是
std::atomic
的成员函数,从 C++11 就存在,才真正执行单条原子 RMW(读-改-写)指令; 编译器不会报错,但行为完全不同:前者是“读旧值 + 写新值”两步,后者是“原子交换”一步。 典型错误写法:
int old = std::exchange(counter, 42);
→ 实际触发非原子 load + store;正确写法:
int old = counter.exchange(42);
什么时候该用 std::atomic::exchange 而不是 compare_exchange? 选
exchange
的核心判断标准是:你是否“不关心当前值,只管换掉它”。 立即学习 “ C++免费学习笔记(深入) ”; 适用场景:
std::atomic
做一次性标志置位(如关机请求)、
std::atomic
替换指针、计数器重置为初值; 不适用场景:实现自旋锁、无锁栈顶更新、引用计数递减——这些都需要“先检查再改”,必须用
compare_exchange_weak
或
compare_exchange_strong
; 性能差异:x86 上
exchange
编译为
XCHG
指令,零失败、无重试;而
compare_exchange
在冲突时需循环重试,但更灵活。 比如设置全局终止信号:
bool was_running = running_flag.exchange(false);
—— 无需知道之前是不是 true,只要确保现在是 false,并拿到上一状态即可。 C知道 CSDN推出的一款AI技术问答工具 下载 memory_order 参数怎么选?别乱设 seq_cst
std::atomic::exchange
默认用
memory_order_seq_cst
,这是最安全也最重的内存序。但在某些场景可以降级: 仅用于单生产者/单消费者标志位(如启动/停止开关),且不依赖其他变量顺序,可用
memory_order_acq_rel
; 若只是纯粹“采样+覆盖”(如日志缓冲区切换),且后续操作不依赖该值的可见性顺序,甚至可尝试
memory_order_relaxed
; 但注意:
memory_order_relaxed
下,编译器和 CPU 可能重排该操作前后的访存,容易引入隐晦 bug;ARM/PowerPC 等弱内存模型平台尤其敏感。 建议:首次实现时全用默认
seq_cst
;压测确认瓶颈后再针对性优化,且必须配合同步点验证语义不变。 std::atomic::exchange 对指针和智能指针的特殊处理 它支持任意可复制/可移动类型,但对指针类操作要格外注意语义: 裸指针交换(如
std::atomic
):交换的是地址值,不涉及所指对象生命周期,务必确保原指针指向资源仍有效或已被安全释放;
std::atomic<:shared_ptr>>
是合法的,但
exchange
只原子交换控制块指针,不保证
T
的构造/析构原子性;
std::unique_ptr
不能直接塞进
std::atomic
(不满足
CopyConstructible
),得用
std::atomic<:shared_ptr>
或手动封装指针 + 引用计数。 常见误用:
std::atomic<:unique_ptr>> ptr;
→ 编译失败;正确做法是用
std::atomic
配合
std::unique_ptr::release()
/
reset()
手动管理。 真正容易被忽略的点是:
exchange
返回的是旧值的副本,不是引用;如果旧值是个大对象(比如含动态分配内存的结构体),频繁调用可能引发意外拷贝开销——这时候得评估是否真需要返回旧值,或者改用
store()
+ 单独
load()
分离操作。

相关文章