首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >C11内存栅栏使用

C11内存栅栏使用
EN

Stack Overflow用户
提问于 2013-10-30 17:29:14
回答 3查看 3.6K关注 0票数 12

即使对于一个简单的2线程通信示例,我也很难用C11原子和memory_fence风格来表示这一点,以获得正确的内存排序:

共享数据:

代码语言:javascript
运行
复制
volatile int flag, bucket;

出厂线:

代码语言:javascript
运行
复制
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);
}

消费者线索:

代码语言:javascript
运行
复制
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);
}

据我所知,以上代码将正确地命令存储在桶中的->标志-存储->标志-加载从桶加载->。但是,我认为在从桶加载到用新数据重写桶之间仍然存在竞争条件。要强制执行桶读之后的命令,我想我需要在桶读和下面的atomic_store之间设置一个显式的atomic_store。不幸的是,似乎没有memory_order参数在前面的负载上强制执行任何东西,甚至memory_order_seq_cst也没有。

一个非常脏的解决方案可能是在使用者线程中重新分配带有虚拟值的bucket:这与使用者只读概念相矛盾。

在较老的C99/GCC世界中,我可以使用传统的__sync_synchronize(),我相信它会足够强大。

什么是更好的C11风格的解决方案来同步这个所谓的反依赖?

(当然,我知道我应该避免这样的低级别编码,并使用可用的高级构造,但我想了解.)

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2013-10-31 13:45:09

要强制执行桶读之后的命令,我想我需要在桶读和下面的atomic_store之间使用一个显式的atomic_store()。

我不认为atomic_thread_fence()调用是必要的:标志更新具有发布语义,防止了前面的任何加载或存储操作被重新排序。见Herb Sutter的正式定义:

一个写发行版执行,毕竟它是按照程序顺序读取和写入的线程。

这将防止在bucket更新之后重新排序flag的读取,而不管编译器选择在何处存储data

这使我想到你对另一个答案的评论:

volatile确保生成ld/st操作,这些操作随后可以用栅栏排序。但是,数据是局部变量,而不是易失性的。编译器可能会将其放入寄存器中,从而避免存储操作。这就使得桶中的负载将与随后的标志重置一起排序。

如果bucket读取不能通过flag的写入版本重新排序,那么这似乎不是一个问题,因此不应该需要volatile (尽管拥有它也可能没有什么坏处)。这也是不必要的,因为大多数函数调用(在本例中是atomic_store_explicit(&flag))充当编译时内存屏障。编译器不会通过非内联函数调用重新排序全局变量的读取,因为该函数可以修改相同的变量。

我也同意@MaximYegorushkin的观点,即当目标是兼容的架构时,您可以改进繁忙的等待pause指令。GCC和国际商会似乎都有_mm_pause(void)的本质(可能相当于__asm__ ("pause;"))。

票数 3
EN

Stack Overflow用户

发布于 2013-10-30 20:30:09

我同意MikeStrobel在他的评论中所说的话。

这里不需要atomic_thread_fence(),因为关键部分从获取开始,最后以发布语义结束。因此,在获取和写入发布后之前,不能对关键部分中的读取进行重新排序。这就是为什么volatile在这里也是不必要的。

此外,我看不出为什么这里不使用(线程)自旋锁。spinlock为您做了类似的繁忙旋转,但它也使用 instruction

暂停内禀用于自旋等待循环,处理器实现动态执行(特别是无序执行)。在旋转等待循环中,暂停内在提高了代码检测锁释放的速度,并提供了特别重要的性能增益。下一条指令的执行会被延迟一段特定于实现的时间.暂停指令不修改体系结构状态。对于动态调度,暂停指令减少了退出自旋循环的代价.

票数 1
EN

Stack Overflow用户

发布于 2013-10-30 21:24:09

直接答覆:

存储是memory_order_release操作意味着编译器必须在存储标志之前发出存储指令的内存围栏。这是必需的,以确保其他处理器在开始解释数据之前查看所释放数据的最终状态。所以,不,你不需要再加一道篱笆。

较长的答覆:

如上所述,编译器将您的atomic_...指令转换成栅栏和内存访问的组合;基本的抽象不是原子负载,而是内存隔离。这就是事情的工作原理,尽管新的C++抽象吸引了您不同的想法。我个人认为,与C++中的令人费解的抽象相比,记忆栅栏更容易思考。

从硬件的角度来看,您需要确保的是负载和存储的相对顺序,即在生产者中写入标记之前完成对桶的写入,并且标记的加载读取的值比使用者中的桶负载更早。

也就是说,你真正需要的是:

代码语言:javascript
运行
复制
//producer
while(true) {
    int value = producer_work();
    while (flag) ; // busy wait
    atomic_thread_fence(memory_order_acquire);  //ensure that value is not assigned to bucket before the flag is lowered
    bucket = value;
    atomic_thread_fence(memory_order_release);  //ensure bucket is written before flag is
    flag = true;
}

//consumer
while(true) {
    while(!flag) ; // busy wait
    atomic_thread_fence(memory_order_acquire);  //ensure the value read from bucket is not older than the last value read from flag
    int data = bucket;
    atomic_thread_fence(memory_order_release);  //ensure data is loaded from bucket before the flag is lowered again
    flag = false;
    consumer_work(data);
}

注意,标签“生产者”和“消费者”在这里是误导的,因为我们有两个进程在玩乒乓球,每个进程依次成为生产者和消费者;只是一个线程产生有用的值,而另一个线程产生“洞”来将有用的值写入.

atomic_thread_fence()是您所需要的,而且由于它直接转换到atomic_...抽象下面的汇编程序指令,所以保证它是最快的方法。

票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/19689872

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档