如何在写入其他std :: atomic之后强制执行std :: atomic?

时间:2016-01-20 09:22:20

标签: c++ multithreading c++11 atomic stdatomic

我有两个线程,Producer和Consumer。数据交换由std :: atomics中的两个指针控制:

std::atomic<TNode*> next = nullptr;
std::atomic<TNode*> waiting = nullptr;

Thread Producer发布准备好的数据,然后 检查等待的值:

TNode* newNext = new TNode( );
// ... fill *newNext ...
next.store( newNext, std::memory_order_release );
TNode* oldWaiting = waiting.load( std::memory_order_seq_cst );
if( oldWaiting == nullptr )
{
  /* wake up Consumer */
}

waiting 上的商店之后,next上的负载来了,这是十分可怕的,但std::memory_order_seq_cst有比我真正需要的更强大的保证,因为我真的只需要修复那两个访问的顺序。 是否可以在不需要memory_order_seq_cst的情况下获取我需要的内存顺序?

以下是图片的其余部分:

线程消费者检查next。如果它发现它为空,则设置waiting以在阻止自身之前通知生产者。

TNode* newCurrent = next.load( std::memory_order_consume );
if( newCurrent == nullptr )
{
  waiting.store( current, std::memory_order_relaxed );
  /* wait, blocking, for next != nullptr */
}
current = newCurrent;

整个事情是生产者 - 消费者队列,它需要锁定低,而不需要所有复杂的机制。 next实际上位于单链表的当前节点内。数据通常以突发形式出现,因此在大多数情况下,消费者会发现一大堆节点已准备就绪;除了罕见的情况,两个线程都只在突发之间进行一次锁定和阻塞/唤醒。

3 个答案:

答案 0 :(得分:2)

您实际上是在寻找memory_order_release的镜像顺序。那是memory_order_acquire

这比你要求的要强一些。 .load之后无法重新排序内存访问。但是,CPU一般不提供部分命令两次访问的方法,因此C ++也没有这种粒度。

理论上C ++也有release/consume排序,但没有人真正需要它。

答案 1 :(得分:2)

要非常小心。您需要在某个地方使用释放围栏和获取围栏以确保您在执行期间执行的写入:

TNode* newNext = new TNode( );
// ... fill *newNext ...

消费者可以看到。

离你最近的地方是“放松”。读取消费者中的原子然后执行获取并开始消费&#39;物体。 在某些(大多数?)架构上可能没有任何影响。

阅读使用获取和释放围栏的演练&#39;这里http://preshing.com/20130922/acquire-and-release-fences/

我无法写出更贴近你正在做的事情的实际例子。制片人/消费者是(面对它)教科书的挑战。

稍微偏离问题。我会使用std::condition_variable。他们为此做好了准备。

稍微偏离问题我对你的锁定策略不太热衷。 这取决于生产者/消费者可能需要多长时间,但生产者和消费者是否会爆发&#39;就像你说的那样阻止白色可能是一个坏主意消费者正在努力。你已经有效地让他们轮流。 你可以做什么(只需要一点点小心)就可以让Producer能够在消费者几乎不受阻碍的情况下推动队列背面的工作(TNodes)。因此,如果消费者需要一段时间生产者可能不构成延迟开销。

这是一个没有设计的设计:

/* wait, blocking, for next != nullptr */

该举起

TNode* newNext = new TNode( );
// ... fill *newNext ...

在下一个工作项目上。 NB:如果消费者逻辑上必须在此之前完成,那么这项任务的并行性的整个想法就会被打破,你也可能顺序完成。

答案 2 :(得分:-1)

简短回答:

编译器可以自由重新排序内存访问(或者甚至在不易失时将它们删除),除了:

如果在指定memory_order_release的加载之前将商店指定为memory_order_acquire,则编译器必须尊重您的意图,而不是将加载重新排序为&#39;发生在&#39;之前。商店。

顺序一致性将实现这一点,而不会给维护者带来麻烦。它在即将推出的arm 8上也是最佳效率的,它将是第一个正确实现负载获取和存储释放指令的处理器。

在这两个演讲中,您可以找到有关c ++原子的所有知识:

https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-1-of-2

https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-2-of-2

我建议在尝试使用原子之前需要查看它们。

在看完它们之后,你可能会意识到你以前对原子学一无所知,即使你认为你做过。对我来说当然就是这种情况。