我编写了以下代码,它有点像一个编写器和一个阅读器的同步队列。不超过1位读者和1位作家。
作者反复调用maybePublish
,它被设计为无锁。相反,读者使用spinUntilFreshAndFetch
。它通过原子变量通知它想要下一个非常新鲜的项目。存储之后,它会旋转原子变量,等待编写器将其设置回0,之后它可以获取共享对象并将其放入自己的副本中。
class Shared {
public:
void maybePublish(const Item &item) {
if (mItemSync.load(std::memory_order_acquire) == 1) {
mItem = item;
mItemSync.store(0, std::memory_order_release);
}
}
void spinUntilFreshAndFetch(Item *copy) {
mItemSync.store(1, std::memory_order_release); // A
while (mItemSync.load(std::memory_order_acquire) != 0) { // B
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
*copy = mItem;
}
private:
Item mItem;
std::atomic_int32_t mItemSync = 0;
};
我的担心是关于A行和B行。我在标准中看不到任何不允许交换这些行的内容。该标准保证释放不会浮动在收购之上,但并不意味着收购不能浮动在释放之上。
另外,我担心它可能会被优化。例如,编译器是否可以假设,在B,mItemSync
除了1(来自A行)之外不能是其他任何东西,并将其转换为无限循环?
根据我看到的教程,如果我使用std::memory_order_seq_cst
,则无法对A和B进行重新排序。我应该这样做吗?
感谢您的任何建议!
答案 0 :(得分:3)
程序很好。
原子意味着:编译器不能在单个线程上重新排序它们,保证波动性(因此没有无限循环)和原子性(操作不可分割)。
获取和释放语义意味着:如果获取操作观察到释放操作的副作用,无论发布完成之前是什么。
如果我们将发布标记为}
并获取为{
。括号内的任何内容都不能按其语义向外移动。你的两个线程看起来像
reader } { { {{ { { R
writer { { {{ { W }
^ ^ ^ ^
1 2 3 4
作者首先反复尝试发布和获取,直到读者发布才会失败。
作者将在读者发布后的某个时间获得。
同时读者反复尝试获取,在作者发布之前也会失败。
读者获得。
请注意这4个操作必须按此顺序进行。写入mItem
保证在2
和3
之间,并且必须在4
之后进行阅读。结合平铺这两个线程仍保留此属性意味着程序正常。