C11内存模型下原子操作与数据写入顺序的正确性问询
问题描述
我拥有两个atomic int类型变量及其他数据,要求代码执行顺序严格遵循以下步骤:
- 写入第一个atomic int
- 写入其他数据
- 写入第二个atomic int
且步骤2绝不能被重排至步骤1之前。请问下述代码是否正确?若不正确,原因是什么?
_Atomic uint32_t begin_id; _Atomic uint32_t end_id; int some_other_data = 0; void bla() { const uint32_t id = ...; atomic_store_explicit(&begin_id, id, memory_order_release); // ...writing some other data... some_other_data = f(x); atomic_store_explicit(&end_id, id, memory_order_release); }
我对内存序的使用存在疑问。
--- 编辑补充 ---
最终我将代码修改为实现seqlock,主要区别在于读取时不使用循环(仅尝试一次),且使用atomic_thread_fence(memory_order_acquire)确保数据仅在序列首次递增后写入,同时无需自旋锁。
代码正确性分析
这段代码无法保证步骤2不会被重排到步骤1之前,核心原因是对memory_order_release的语义理解有误:
memory_order_release的作用是:当前线程中,该原子操作之前的所有内存写操作,不能被重排到这个原子操作之后;但它完全不限制该原子操作之后的写操作被重排到它之前。- 你给
begin_id的写入加了memory_order_release,只能确保begin_id的写入不会被挪到它之前的代码后面,却拦不住编译器或CPU把后面的some_other_data = f(x)重排到begin_id写入的前面,直接违反了你的顺序要求。
要满足“步骤2绝不能在步骤1之前”的要求,有两种轻量可行的调整方式:
- 在
begin_id写入后添加写屏障:
屏障会强制所有屏障前的写操作完成后,才执行屏障后的操作,彻底阻止重排。atomic_store_explicit(&begin_id, id, memory_order_relaxed); atomic_thread_fence(memory_order_release); some_other_data = f(x); - 把
begin_id写入的内存序改为memory_order_seq_cst,但这种全序内存序会带来额外性能开销,非必要不推荐。
关于简化版seqlock的注意事项
你修改后的seqlock去掉了循环重试、用atomic_thread_fence(memory_order_acquire)替代自旋锁,需要明确适用场景:
- 放弃循环重试意味着,一旦读取时刚好遇上写入操作,必然会读到不一致的数据,只适合写入频率极低、对数据一致性容忍度较高的场景;
atomic_thread_fence(memory_order_acquire)能确保屏障后读取数据时,序列值的递增操作已完成,避免读到未完全写入的脏数据,但解决不了“读取中途写入导致数据撕裂”的问题——这也是标准seqlock保留循环重试的核心原因。
内容的提问来源于stack exchange,提问作者Kevin Meier
相关产品推荐
相关产品推荐

