1. 从锁的枷锁到原子操作的解放:为什么我们需要无锁数据结构
大家好,我是老陈,一个在C++高性能服务领域摸爬滚打了十多年的老码农。今天我们不聊那些高大上的架构设计,就聊一个非常具体、但在高并发场景下能让你程序性能“起飞”的技术点:如何用好 std::atomic 的 exchange 和 compare_exchange 来构建无锁数据结构。
我记得刚入行那会儿,处理并发问题,脑子里第一个蹦出来的词就是“锁”。互斥锁、读写锁、自旋锁,各种锁用得不亦乐乎。锁确实简单直观,就像给共享资源上了一道门,谁想用谁拿钥匙。但后来在做一个高频交易系统的核心引擎时,我彻底被锁给坑惨了。那个系统每秒要处理几十万笔订单,我们用了大量的细粒度锁来保护各种队列和计数器。上线压测时,性能曲线简直没法看——线程数增加到一定程度后,吞吐量不升反降,CPU大部分时间都在“等待锁”和“线程切换”上,而不是真正干活。这就是锁的典型弊端:阻塞、死锁风险、以及最要命的锁竞争带来的性能悬崖。
正是那次惨痛经历,逼着我开始深入研究无锁编程。无锁(Lock-Free)并不是说完全不用同步,而是指通过硬件提供的原子指令(比如CAS,Compare-And-Swap)来实现同步,从而避免传统锁机制带来的阻塞。它的核心优势在于免疫死锁、减少线程挂起/唤醒的开销,并且在竞争激烈时,性能表现往往比锁更平滑、更可预测。而C++标准库中的 std::atomic 模板,就是我们进入无锁世界的大门钥匙。其中,exchange 和 compare_exchange 又是这把钥匙上最常用、最核心的两个齿。
简单来说,你可以把 std::atomic 想象成一个被魔法保护的盒子,任何线程对这个盒子里的值进行读写、修改,在别的线程看来都是“一瞬间”完成的,不会看到中间状态。exchange 就是“不管盒子里现在是啥,我直接塞个新东西进去,并把旧东西拿出来”。而 compare_exchange 则是“我先猜一下盒子里现在是A,如果猜对了,我就换成B;如果猜错了,我就知道现在实际是啥,然后重新猜”。这个“猜”和“换”的动作,也是原子的。理解了这两个基本操作,你就能搭建出各种高效的无锁队列、栈、计数器甚至更复杂的数据结构。
2. 原子操作的基石:深入理解 exchange 与 compare_exchange
在动手写代码之前,我们必须把这两个操作的脾气秉性摸透。很多新手觉得它们神秘,其实拆开看,逻辑非常直接。
2.1 std::atomic::exchange:简单粗暴的“换岗”
exchange 的功能如其名:交换。它的函数签名很简单:
T exchange(T desired, std::memory_order order = std::memory_order_seq_cst) noexcept;
你传给它一个新值 desired,它二话不说,把原子对象当前的值替换成 desired,同时把替换之前的旧值返回给你。这个操作总是成功,因为它不关心旧值是什么。
我把它比喻成哨兵的换岗。想象一个原子布尔变量 guard_on_duty 表示是否有哨兵在站岗。换岗的流程可以是:
std::atomic<bool> guard_on_duty{false};
// 哨兵A来上岗
bool old_value = guard_on_duty.exchange(true);
// old_value 是 false,表示之前没人站岗,A成功上岗。
// 如果此时哨兵B也调用 exchange(true):
bool old_value_for_b = guard_on_duty.exchange(true);
// old_value_for_b 是 true,表示A已经在岗了,但B还是强行“换岗”了,现在变成B在岗。
看到了吗?exchange 不保证“只有我能上岗”,它只保证“换岗”这个动作是原子的,不会出现A和B同时看到 guard_on_duty 为 false 然后都认为自己上岗成功的混乱局面。它返回的旧值告诉你操作之前的状态,你可以根据这个返回值来决定后续逻辑(比如,如果发现已经有人站岗了,B可以选择离开)。
一个非常经典的应用场景是实现一个简单的自旋锁(虽然我们谈无锁,但用它来实现锁本身很有教育意义):
class SimpleSpinLock {
std::atomic<bool> locked_{false};
public:
void lock() {
// 一直尝试把 locked_ 从 false 换成 true
while (locked_.exchange(true, std::memory_order_acquire)) {
// 如果换之前是 true,说明锁被别人持有,那就循环等待(自旋)
// 这里可以加一点CPU暂停指令(如 _mm_pause)优化
}
// 跳出循环时,说明我们成功把 false 换成了 true,获得了锁
}
void unlock() {
// 把 locked_ 设置回 false,释放锁
locked_.store(false, std::memory_order_release);
}
};
这里 lock() 中的循环,就是不断尝试“换岗”,直到自己成功为止。exchange 的原子性保证了同一时刻只有一个线程能成功将 false 换成 true。
2.2 std::atomic::compare_exchange:谨慎聪明的“条件换岗”
如果说 exchange 是个莽夫,那 compare_exchange (CAS) 就是个智者。它是无锁编程的灵魂。它有两个版本:compare_exchange_weak 和 compare_exchange_strong。我们先看共性。
函数签名看起来复杂一点:
bool compare_exchange_weak(T& expected, T desired,
std::memory_order success,
std::memory_order failure) noexcept;
bool compare_exchange_strong(T& exp



被折叠的 条评论
为什么被折叠?



