我最近正在学习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
谢谢。
因为这不是内存顺序的工作方式
我们在原子操作中添加了一个内存屏障,以实现两件事:
- 以防止松弛的原子操作和非原子操作被重新排序
- 跨线程同步非原子数据
我在这里写了一个答案,更清楚地解释了这两点。
当一个协程在一个线程中挂起,在另一个线程恢复时,不需要额外的同步*,协程上的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。内存顺序操作生成内存栅栏,以便其他对象可以正确地提交到共享内存/从共享内存读取。