【发布时间】:2018-02-25 13:52:17
【问题描述】:
我编写了以下代码,它的作用有点像一个写入器和一个读取器的同步队列。永远不会超过 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。我应该这样做吗?
感谢您的建议!
【问题讨论】:
-
加载顺序在存储之后,并会观察此存储的结果。即使对于普通的非原子变量也是如此——这两个操作都由同一个线程执行。
-
谢谢!这对我来说很有意义。但是,作者可以观察到由于这里的重新排序而产生的任何奇怪的效果吗?而且大概,编译器不能做那个无限循环优化?
-
编译器不能优化掉原子操作,它必须假设一个原子可以随时改变它的值,通过当前线程之外的一些动作;这就是原子的意义所在。我不确定你所说的“奇怪的影响”是什么意思。对于它的价值,我没有立即发现您的代码有任何问题,但我不会远程称自己为专家。