Jos*_*v E 12 c multithreading atomic memory-fences c11
即使对于一个简单的双线程通信示例,我也难以用C11原子和memory_fence样式来表达它以获得正确的内存排序:
共享数据:
volatile int flag, bucket;
Run Code Online (Sandbox Code Playgroud)
生产者线程:
while (true) {
int value = producer_work();
while (atomic_load_explicit(&flag, memory_order_acquire))
; // busy wait
bucket = value;
atomic_store_explicit(&flag, 1, memory_order_release);
}
Run Code Online (Sandbox Code Playgroud)
消费者线程:
while (true) {
while (!atomic_load_explicit(&flag, memory_order_acquire))
; // busy wait
int data = bucket;
atomic_thread_fence(/* memory_order ??? */);
atomic_store_explicit(&flag, 0, memory_order_release);
consumer_work(data);
}
Run Code Online (Sandbox Code Playgroud)
据我所知,上面的代码可以正确地命令存储在桶中 - > flag-store - > flag-load - > load-from-bucket.但是,我认为在load-from-bucket和使用新数据重新写入桶之间仍存在竞争条件.要在读取桶之后强制执行订单,我想我需要atomic_thread_fence()在读取桶和以下atomic_store之间进行显式操作.不幸的是,似乎没有任何memory_order论据可以对前面的负载强制执行任何操作,甚至没有memory_order_seq_cst.
一个非常脏的解决方案可能是bucket在消费者线程中使用虚拟值重新分配:这与消费者只读概念相矛盾.
在较旧的C99/GCC世界中,我可以使用__sync_synchronize()我认为足够强大的传统.
什么是更好的C11风格的解决方案来同步这种所谓的反依赖?
(当然我知道我应该更好地避免这种低级编码并使用可用的更高级别的结构,但我想了解...)
为了强制执行存储桶读取后的订单,我想我需要在存储桶读取和后面的atomic_store之间有一个显式的atomic_thread_fence()。
我不认为该atomic_thread_fence()调用是必要的:标志更新具有释放语义,可防止任何先前的加载或存储操作在其中重新排序。请参阅 Herb Sutter 的正式定义:
写释放在按程序顺序位于其之前的同一线程执行所有读取和写入操作之后执行。
bucket这应该可以防止在更新后重新排序 的读取flag,无论编译器选择在何处存储data.
这让我想起你对另一个答案的评论:
确保
volatile生成 ld/st 操作,随后可以使用栅栏对其进行排序。但是,数据是局部变量,不是易失性的。编译器可能会将其放入寄存器中,从而避免存储操作。这使得桶中的负载需要通过随后的标志重置来排序。
bucket如果读取不能在flag写入释放之后重新排序,那么这似乎不是问题,因此volatile没有必要(尽管拥有它可能也没有什么坏处)。这也是不必要的,因为大多数函数调用(在本例中atomic_store_explicit(&flag)为 )充当编译时内存屏障。编译器不会在非内联函数调用之后重新排序全局变量的读取,因为该函数可以修改同一变量。
我也同意@MaximYegorushkin 的观点,pause即在针对兼容架构时,您可以通过指令来改善忙碌等待。GCC 和 ICC 似乎都有_mm_pause(void)内在函数(可能相当于__asm__ ("pause;"))。