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

原子操作的内存序、happens-before关系及代码相关技术咨询

原子操作内存序问题分析

示例代码

static int zero = 0;

static int proc_enter(struct proc_context* ctx, struct lkf_node* node)
{
    __atomic_store_n(&node->next, NULL, __ATOMIC_RELAXED);  // 0
    __atomic_store_n(&ctx->list.tail, &node->next, __ATOMIC_RELEASE);  // 1


    int n = (int) __atomic_compare_exchange_n(&ctx->stat, &zero, 1, false, __ATOMIC_RELEASE, // 2
                                              __ATOMIC_RELAXED);
    return n - 1;
}

static int proc_leave(struct proc_context* ctx)
{
    __atomic_store_n(&ctx->stat, 0, __ATOMIC_RELAXED);  // 3

    __atomic_thread_fence(__ATOMIC_ACQ_REL); // 4

    if (__atomic_load_n(&ctx->list.tail, __ATOMIC_RELAXED) == &ctx->list.root.next) { // 5
        return 0;
    }

    int n = (int) __atomic_compare_exchange_n(&ctx->stat, &zero, 1, false, __ATOMIC_RELAXED,
                                              __ATOMIC_RELAXED);
    return n - 1;
}

问题解答

1. 是否需要__ATOMIC_RELEASE内存序来保证//0操作happens before//1操作?

不需要。同一线程内的操作天然遵循程序顺序(Program Order),不管原子操作的内存序是什么,当前线程里//0的执行必然早于//1,且//0的结果对//1可见。__ATOMIC_RELAXED已经足够,不需要额外的RELEASE内存序来保证线程内的happens-before关系。

2. __atomic_compare_exchange_n中的__ATOMIC_RELEASE是否是保证//1操作happens before//2操作所必需的?

不需要。和上面的逻辑一样,同一线程内的操作天然有程序顺序的happens-before约束,//1的写操作必然早于//2的CAS操作,且结果对//2可见。这里的RELEASE内存序是用来和其他线程的ACQUIRE操作建立跨线程同步的,不是为了保证线程内的执行顺序。

3. 是否需要__ATOMIC_ACQ_REL内存序来保证//5操作happens before//3操作?

首先要纠正逻辑:代码里//3是先执行的写操作(写入ctx->stat为0),//5是后执行的读操作(读取ctx->list.tail),你实际需要保证的是**//3的写操作完成后再执行//5的读操作**,也就是阻止CPU将//5的读重排到//3的写之前。

当前的__ATOMIC_ACQ_REL栅栏确实能实现这个效果:它会阻止栅栏之前的存储操作(//3)被重排到栅栏之后,同时阻止栅栏之后的加载操作(//5)被重排到栅栏之前。但注意,需求不是//5 happens before //3,而是反过来,__ATOMIC_ACQ_REL是用来保证这个正向顺序的。

4. 如果__atomic_thread_fence能保证//3操作happens before//5操作,那么它是否是实现该约束的唯一方式?

不是唯一方式,可以通过给原子操作本身指定内存序来替代独立栅栏:

  • 方案一:将//3的__ATOMIC_RELAXED改为__ATOMIC_RELEASE,同时将//5的__ATOMIC_RELAXED改为__ATOMIC_ACQUIRE。RELEASE写会强制自身及之前的存储操作完成,且阻止后续操作重排到它前面;ACQUIRE读会强制自身及之后的加载操作不被重排到前面,两者结合同样能保证//3的写在//5的读之前执行,不会被CPU重排。
  • 方案二:仅将//3的内存序改为__ATOMIC_RELEASE。RELEASE写本身会强制之前的存储提交到内存,并且阻止后续的加载操作重排到它前面,这样//5的读也不会跑到//3的写之前。

结合你提到的CPU加载-存储队列逻辑:

  1. 写入ctx->stat(//3)
  2. 刷新存储队列:对应RELEASE写或栅栏的存储屏障,确保之前的写操作都提交到内存,不会"下沉"到后续操作之后
  3. 刷新加载队列:对应ACQUIRE读或栅栏的加载屏障,确保后续的读操作不会被"提升"到前面操作之前
  4. 加载ctx->list.tail(//5)

用原子操作的内存序替代独立栅栏,本质是把屏障逻辑绑定到具体的原子操作上,效果和独立栅栏完全一致,不需要额外的栅栏指令。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 03:54:53