为什么不使用std::memory_order_aq_rel



我最近正在学习cppcoro项目的代码。我有一个问题。

https://github.com/lewissbaker/cppcoro/blob/master/lib/async_auto_reset_event.cpp#L218https://github.com/lewissbaker/cppcoro/blob/master/lib/async_auto_reset_event.cpp#L284

if (waiter->m_refCount.fetch_sub(1, std::memory_order_release) == 1) // #218
{
waiter->m_awaiter.resume();
}

使用第218行中的memory_order_release写入,使用memory_orderAcquire标志的m_refCount可以在第284行中正确加载值。没关系,但是fetch_sub是一个RMW操作。为了正确读取第284行中的修改,是否还需要memory_order_require标志?所以我想知道m_refCount为什么不在第218行和第284行使用memory_order_aq_rel?

return m_refCount.fetch_sub(1, std::memory_order_acquire) != 1; // #284

谢谢。

因为这不是内存顺序的工作方式

我们在原子操作中添加了一个内存屏障,以实现两件事:

  1. 以防止松弛的原子操作和非原子操作被重新排序
  2. 跨线程同步非原子数据

我在这里写了一个答案,更清楚地解释了这两点。

当一个协程在一个线程中挂起,在另一个线程恢复时,不需要额外的同步*,协程上的cppreference说:

请注意,由于协程在进入awaiter.await_suspend((之前已完全挂起,因此该函数可以自由地跨线程传输协程句柄,而无需额外的同步。

至于重新排序?实际逻辑(waiter->m_awaiter.resume();(被一个大的fat-if语句包围。编译器无论如何都不能在fetch_sub之前对恢复进行重新排序,因为这样会忽略if语句的作用并破坏代码的逻辑。

所以,我们不需要任何其他的记忆顺序,只是放松。fetch_XXX是RMW操作这一事实毫无意义——我们为正确的用例使用了正确的内存顺序。

如果你喜欢cppcoro,请尝试我自己的协同程序库concurrentpp。

*更正确的说法是:除了从一个线程传递coroutine_handle所需的同步之外,不需要额外的同步到另一个。

对原子变量本身的操作都不会导致数据争用。

std::memory_order_release意味着所有修改的缓存数据将被提交到共享存储器/RAM。内存顺序操作生成内存栅栏,以便其他对象可以正确地提交到共享内存/从共享内存读取。

相关内容

  • 没有找到相关文章

最新更新