如何用无锁原子操作替代互斥锁实现C/C++线程间共享数据的竞态条件防护?
如何用无锁原子操作替代互斥锁实现C/C++线程间共享数据的竞态条件防护?
哈哈,这个问题我当初刚接触无锁编程时也卡了好久!其实核心痛点你已经摸到了:单独用原子类型的读或写还不够——因为你原逻辑里的「判断shared_var是否为3,再减1」是两个步骤,哪怕每一步都是原子的,放在一起就有被别的线程打断的窗口,这就是你担心的那种“刚判断完就被改了”的情况。
要解决这个问题,得把「判断+修改」这一整套逻辑变成原子操作,而硬件层面的CAS(比较并交换)指令就是干这个的——它能把“比较当前值、如果符合预期就替换成新值”这两个动作打包成一个不可打断的硬件指令,完美解决中间被打断的问题。
先给你直接上可运行的代码示例
先看C++的标准实现,用std::atomic和CAS操作:
#include <atomic> std::atomic<int> shared_var = 0; void thread1() { int expected; // 循环重试,处理竞争导致的CAS失败 do { // 原子读取当前的shared_var值 expected = shared_var.load(); // 如果当前值不是3,直接退出,没必要继续尝试 if (expected != 3) { break; } // 核心:CAS操作,原子完成「比较+替换」 // 逻辑:如果shared_var现在还是expected(也就是3),就把它改成expected-1(2),返回true // 如果期间被别的线程改了,返回false,同时把expected更新成当前的真实值 } while (!shared_var.compare_exchange_weak(expected, expected - 1)); } void thread2() { // 原子自增,直接用标准库的fetch_add,这是原子操作 shared_var.fetch_add(1); }
如果是用C语言(C11及以上),可以用标准的stdatomic.h:
#include <stdatomic.h> atomic_int shared_var = ATOMIC_VAR_INIT(0); void thread1() { int expected; do { expected = atomic_load(&shared_var); if (expected != 3) { break; } } while (!atomic_compare_exchange_weak(&shared_var, &expected, expected - 1)); } void thread2() { atomic_fetch_add(&shared_var, 1); }
来拆解你关心的核心问题
你问:如果thread1刚检测到shared_var==3,thread2就把它改成4,这时候thread1去减1会发生什么?
- 用CAS的话,thread1的
compare_exchange_weak会先检查当前shared_var的值是不是之前读到的3——这时候已经是4了,所以CAS会直接返回false,不会执行任何修改。 - 同时,
compare_exchange_weak会把expected变量更新成当前的真实值(也就是4),然后循环会继续:这时候我们读到的expected是4,不等于3,就直接break退出,不会做无效操作。
再给你划几个无锁编程的关键要点
- 为什么不能直接原子读+原子写?:哪怕
shared_var是原子类型,if (shared_var ==3) shared_var--;这种写法依然有问题——因为shared_var ==3是原子读,shared_var--是原子写,但这两个步骤之间有时间差,别的线程完全可以在中间插一脚,这就是你担心的竞态。 - CAS是无锁的核心:它是硬件提供的单一原子指令,没有中间状态,所以不会被打断。
- 循环重试是标配:只要有竞争,CAS就可能失败,这时候我们不能直接放弃,而是要重新读取最新值,再尝试一次——这和自旋锁有点像,但它不是锁,只是针对单个操作的重试,开销比互斥锁小很多(不需要内核态切换)。
- 不是所有场景都适合无锁:如果你的场景竞争特别激烈,重试次数会暴增,这时候性能可能还不如互斥锁;但低竞争场景下,无锁的优势就很明显了。
- 内存顺序别乱改:上面的代码用的是默认的
std::memory_order_seq_cst(最严格的内存顺序),新手先别碰更宽松的内存顺序,不然很容易写出看起来正常但实际有内存可见性bug的代码。
总的来说,无锁编程的核心就是用CAS把多步逻辑打包成原子操作,通过循环处理竞争情况——这样就完全不需要互斥锁,靠硬件层面的原子指令就能实现线程安全的操作啦!




