多存储操作内存序问题:生产者-消费者同步代码的正确性与修复
问题分析与修复
问题本质
你担心的操作重排风险确实存在,且会直接导致断言失败。因为所有原子操作都使用了memory_order_relaxed,这种内存序不保证任何跨线程的操作顺序可见性——编译器、CPU完全可以把生产者里的barrier[0].store/barrier[1].store重排到runcnt.store(2)之前执行。
一旦出现这种重排,某个消费者线程会先看到自己的barrier被设为1,随后执行runcnt.fetch_sub(1),但此时生产者还未将runcnt设为2,prev的值就是初始的0,直接触发assert(prev > 0)失败。
修复方案
核心是要在生产者的runcnt.store(2)和两个barrier.store之间建立**先行发生(happens-before)**关系,确保消费者看到barrier的修改时,一定能看到runcnt已经被设为2。
方案1:用Release-Acquire内存序配对
修改生产者的runcnt.store为memory_order_release,消费者的barrier[index].exchange为memory_order_acquire:
extern atomic<int> barrier[2]; atomic_int runcnt{0}; void producer() { // 用release确保后续的barrier store一定在这个操作之后可见 runcnt.store(2, memory_order_release); barrier[0].store(1, memory_order_relaxed); barrier[1].store(1, memory_order_relaxed); while (runcnt.load(memory_order_relaxed)) { cpu_pause(); } } void consumer(unsigned index) { while (true) { // 用acquire确保看到barrier的修改时,生产者的runcnt.store已经可见 if (barrier[index].exchange(false, memory_order_acquire)) { int prev = runcnt.fetch_sub(1, memory_order_relaxed); assert(prev > 0); } } }
方案2:显式插入内存栅栏
如果不想修改太多内存序,可以在生产者的runcnt.store之后插入release栅栏,消费者的exchange用acquire:
extern atomic<int> barrier[2]; atomic_int runcnt{0}; void producer() { runcnt.store(2, memory_order_relaxed); // 栅栏确保之前的store操作一定在之后的store之前被其他线程看到 std::atomic_thread_fence(std::memory_order_release); barrier[0].store(1, memory_order_relaxed); barrier[1].store(1, memory_order_relaxed); while (runcnt.load(memory_order_relaxed)) { cpu_pause(); } } void consumer(unsigned index) { while (true) { if (barrier[index].exchange(false, memory_order_acquire)) { int prev = runcnt.fetch_sub(1, memory_order_relaxed); assert(prev > 0); } } }
两种方案都能保证:消费者看到barrier被设置时,runcnt的值已经是2,fetch_sub得到的prev必然大于0,断言不会触发。
内容的提问来源于stack exchange,提问作者Roman
相关产品推荐
相关产品推荐

