You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用无锁原子操作替代互斥锁实现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把多步逻辑打包成原子操作,通过循环处理竞争情况——这样就完全不需要互斥锁,靠硬件层面的原子指令就能实现线程安全的操作啦!

火山引擎 最新活动