You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免get()与set()间的竞态条件?附代码场景分析

解决foo变量的竞态条件问题

你已经精准抓住了问题核心:ActTask中存在检查-然后-执行的竞态窗口——在读取foo到写入修改后的值之间,ResetTask可能抢先修改foo,导致最终写入的是基于旧值的无效计算结果。咱们来逐一分析你提到的方案,并给出更可靠的解决思路:

现有方案的局限性

  1. 修改后的ActTask(重新读取foo后计算)

    if (foocopy > 0) {
        x = AccessPeripheral1();
        setfoo( getfoo() - x);
    }
    

    这个方案确实降低了竞态发生的概率,但本质上仍有漏洞:调用getfoo()和setfoo()之间依然可能被ResetTask抢占,导致getfoo()获取的值在写入前又被修改,最终结果依然不符合预期。

  2. 全局临界区方案
    把ActTask或ResetTask的逻辑整体包裹进临界区,确实能解决竞态,但正如你担心的,嵌套临界区会大幅提升代码复杂度,不仅容易引发死锁,还会扩大临界区范围、降低系统并发性能,这个方案确实应该尽量避免。

最优解决方案:封装原子化的foo操作

核心思路是把“读取foo→检查条件→修改foo”的整个逻辑封装成一个原子操作,避免让任务直接处理锁的细节,同时最小化临界区范围。

方案1:基于互斥锁的原子操作封装

扩展foo的操作接口,新增一个专门用于“当foo>0时减去delta”的原子函数:

// 新增原子更新函数
bool updateFooSubtract(int32_t delta) {
    MutexLock(fooMutex);
    bool operationSuccess = false;
    if (foo > 0) {
        foo -= delta;
        operationSuccess = true;
    }
    MutexUnlock(fooMutex);
    return operationSuccess;
}

然后修改ActTask,直接调用这个封装好的函数:

void ActTask() {
    while(1) {
        int32_t x = AccessPeripheral1();
        // 无需自己处理锁,直接调用原子更新接口
        updateFooSubtract(x);
        wait(actPeriod);
    }
}

ResetTask保持原有逻辑即可,因为setfoo()本身已经是原子操作,且AccessPeripheral2()有自己的临界区,不要把它放进foo的锁里(避免嵌套锁风险):

void ResetTask() {
    while(1) {
        setfoo(resetvalue);
        AccessPeripheral2(); // 独立临界区,与foo的锁分离
        wait(resetPeriod);
    }
}

这个方案的优势:

  • 临界区范围极小,只包含foo的检查和修改,对并发性能影响低
  • 所有对foo的修改逻辑都封装在接口中,避免任务直接操作锁,降低出错概率
  • 彻底消除了竞态条件,因为整个“检查+修改”操作是原子完成的

方案2:无锁原子操作(基于CAS)

如果你的平台支持C11标准的原子操作(或对应编译器的扩展),可以用**比较交换(CAS)**实现无锁的原子更新,避免互斥锁的开销和死锁风险:

#include <stdatomic.h>

// 把foo改为原子变量
_Atomic int32_t foo;

// 原子读取函数
int32_t getfoo(void) {
    return atomic_load(&foo);
}

// 原子写入函数
void setfoo(int32_t value) {
    atomic_store(&foo, value);
}

// 无锁原子更新:当foo>0时减去delta
bool updateFooSubtract(int32_t delta) {
    int32_t currentVal = atomic_load(&foo);
    while (currentVal > 0) {
        int32_t newVal = currentVal - delta;
        // 如果当前foo的值还是currentVal,就更新为newVal;否则重试
        if (atomic_compare_exchange_weak(&foo, &currentVal, newVal)) {
            return true;
        }
        // 若更新失败,currentVal会被自动更新为foo的最新值,进入下一轮循环
    }
    return false;
}

ActTask和ResetTask的修改方式和方案1一致。这个方案适合对性能要求较高的场景,没有锁的开销,但需要注意CAS操作可能会有重试(当foo频繁被修改时),不过在你的业务场景中,重试次数通常会很少。

总结

  • 避免将大段无关逻辑(如调用AccessPeripheralX())放进foo的临界区,防止嵌套锁和临界区过大的问题
  • 优先选择封装原子操作接口的方式,把锁或无锁逻辑隐藏在底层接口中,上层任务只需要调用接口即可
  • 尽量不要让上层任务直接处理锁的细节,减少人为出错的可能

内容的提问来源于stack exchange,提问作者calofr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:41:40