1

我有两个线程,生产者和消费者。数据交换由 std::atomics 内的两个指针控制:

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

线程生产者发布准备好的数据,然后检查等待的值:

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至关重要的是,在 store on 之后加载 onnext,但std::memory_order_seq_cst它的保证比我真正需要的要强得多,因为我真的只需要固定这两个访问的顺序。是否可以在不需要的情况下获得我需要的内存顺序memory_order_seq_cst

这是图片的其余部分:

线程消费者检查next。如果它发现它是空的,它waiting会在阻塞自己之前向 Producer 发出信号。

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实际上是在单链表的当前节点内。数据通常是突发的,因此在大多数情况下,消费者会发现一大堆节点可供消费;除了极少数情况外,两个线程都只在突发之间经历一次锁定和阻塞/唤醒。

4

3 回答 3

2

您本质上是在寻找memory_order_release. 那就是memory_order_acquire

这比您要求的要强一些。在.load. 但是,CPU 通常不提供对两个访问进行部分排序的方法,因此 C++ 也没有这种粒度。

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

于 2016-01-20T11:10:22.273 回答
2

要非常小心。您将需要一个释放围栏和一个获取围栏,以确保您在以下期间执行的写入:

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

对消费者可见。

您可以做的最接近的事情是在消费者中执行原子的“轻松”读取,然后执行获取并开始“消费”对象。在一些(大多数?)可能没有效果的架构上。

在http://preshing.com/20130922/acquire-and-release-fences/阅读“使用获取和释放围栏的演练” 。

我无法写出更接近于你正在做什么的工作示例的东西。生产者/消费者是(面对它)教科书的挑战。

有点题外话了。我会用一个std::condition_variable. 他们是为此而生的。

稍微远一点的问题我不太热衷于你的锁定策略。这取决于生产者/消费者可能需要多长时间,但如果生产者像你所说的那样“爆发”,那么阻止白人消费者正在工作可能是个坏主意。你有效地让他们轮流。您可以做的(只需稍加注意)就是让生产者能够在队列后面的(TNodes)上推动工作,几乎不受消费者的阻碍。因此,如果消费者需要一段时间,生产者可能不会构成延迟开销。

那就是做一个没有的设计:

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

这是坚持

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

在下一个工作项上。注意:如果消费者在逻辑上必须在此之前完成,那么这个任务的并行性的整个想法就会被破坏,你还不如去顺序。

于 2016-01-20T16:36:06.737 回答
-1

简短的回答:

编译器可以自由地重新排序内存访问(甚至在非易失性时忽略它们),除了:

如果您将存储指定memory_order_release在加载指定之前memory_order_acquire,则编译器需要尊重您的意图,而不是将加载重新排序为“发生在”存储之前。

顺序一致性将实现这一点,而不会让维护者头疼。它在即将推出的 arm 8 上也具有最佳效率,这将是第一个正确实现指令加载-获取和存储-释放的处理器。

您可以在这两个讲座中找到您需要了解的有关 c++ atomics 的所有信息:

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

我建议他们在尝试任何原子操作之前需要查看。

看完之后,您可能会意识到您以前对原子一无所知,即使您认为自己知道。这对我来说当然是这样。

于 2016-01-20T09:36:18.643 回答