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

C语言自定义互斥算法中线程/核心间写操作未正确传播的原因排查

C语言自定义互斥算法中线程/核心间写操作未正确传播的原因排查

我来帮你拆解这个问题——你的互斥逻辑存在一个关键漏洞,导致偶尔会出现两个线程同时进入临界区的情况,进而让counter只被递增一次。咱们一步一步来分析:

1. 核心问题:互斥逻辑的「时间窗口」漏洞

先看线程t1的等待循环逻辑:

while (aload(&shared->f2, mo_acquire)) {
    astore(&shared->f1, false, mo_release);
    thrd_yield();
    astore(&shared->f1, true, mo_release);
}

当t1检测到t2已经设置f2=true时,它会先把自己的f1设为false、让出CPU,再重新把f1设为true。这个过程存在一个致命的时间窗口:

  • 假设t2此时正在执行自己的while (aload(&shared->f1, mo_acquire))循环,当t1把f1设为false的瞬间,t2的循环条件不再满足,会直接退出循环进入临界区执行shared->counter++。
  • 而t1在thrd_yield()之后重新设置f1=true,再次检查f2时,t2可能已经执行完临界区并把f2设为false了。此时t1的循环条件也不满足,会跟着进入临界区。
  • 最终结果就是两个线程同时操作counter,由于counter++是非原子操作,竞态条件会导致它只被实际递增一次。

2. 关于内存序的使用:你的用法是正确的,但不是问题根源

你用mo_release存储标志位、mo_acquire加载标志位的方式是符合C内存模型要求的:

  • mo_release语义确保:shared->counter++的所有副作用,都会在astore(&shared->f1, false, mo_release)完成前被提交,其他线程加载f1=false时能看到这些更新。
  • mo_acquire语义确保:加载f2/f1时,会同步到其他线程之前用mo_release存储的所有操作的结果。

你观察到汇编里没有显式的lock前缀或内存栅栏是正常的——x86架构的CPU本身就保证了store的release语义和load的acquire语义(除了特定的写合并场景),所以编译器不需要生成额外的屏障指令。

3. 修复方案:改用Peterson算法的正确实现

你需要引入一个turn变量来消除时间窗口,这就是经典Peterson算法的核心逻辑。修改后的代码如下:

第一步:修改共享结构体,新增turn变量

typedef struct {
    int counter;
    atomic_bool f1;
    atomic_bool f2;
    atomic_int turn; // 新增:标记当前优先进入临界区的线程
} Shared;

第二步:初始化turn变量

在makeShared()函数中添加:

atomic_init(&shared->turn, 0);

第三步:修正线程逻辑

static int t1(Shared* const shared) {
    astore(&shared->f1, true, mo_release);
    astore(&shared->turn, 2, mo_release); // 声明:我愿意让t2优先进入
    // 等待条件:t2想进入,且当前轮到t2
    while (aload(&shared->f2, mo_acquire) && aload(&shared->turn, mo_acquire) == 2) {
        thrd_yield();
    }
    // 临界区
    shared->counter++;
    astore(&shared->f1, false, mo_release);
    return 0;
}

static int t2(Shared* const shared) {
    astore(&shared->f2, true, mo_release);
    astore(&shared->turn, 1, mo_release); // 声明:我愿意让t1优先进入
    // 等待条件:t1想进入,且当前轮到t1
    while (aload(&shared->f1, mo_acquire) && aload(&shared->turn, mo_acquire) == 1) {
        thrd_yield();
    }
    // 临界区
    shared->counter++;
    astore(&shared->f2, false, mo_release);
    return 0;
}

Peterson算法通过turn变量明确指定“哪个线程优先”,彻底消除了两个线程同时进入临界区的时间窗口,保证了严格的互斥。

4. 额外建议

  • 如果你只是学习用,这个实现没问题;但实际项目中,应该直接用标准库提供的互斥原语(比如mtx_t),它们经过了严格的测试和优化。
  • 可以用tsan(ThreadSanitizer)来检测竞态条件,它能快速帮你定位这类线程同步问题,命令是:gcc -O3 -Wall -Wextra -fsanitize=thread your_code.c -o your_program。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 07:44:32