C ++:重新排序原子库(发布)和加载(获取)

时间:2018-02-25 04:55:36

标签: c++ atomic

我编写了以下代码,它有点像一个编写器和一个阅读器的同步队列。不超过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进行重新排序。我应该这样做吗?

感谢您的任何建议!

1 个答案:

答案 0 :(得分:3)

程序很好。

原子意味着:编译器不能在单个线程上重新排序它们,保证波动性(因此没有无限循环)和原子性(操作不可分割)。

获取和释放语义意味着:如果获取操作观察到释放操作的副作用,无论发布完成之前是什么。

如果我们将发布标记为}并获取为{。括号内的任何内容都不能按其语义向外移动。你的两个线程看起来像

reader             } {  {  {{   {   { R
writer {    {   {{    {  W       }
                   ^  ^          ^  ^
                   1  2          3  4
  1. 作者首先反复尝试发布和获取,直到读者发布才会失败。

  2. 作者将在读者发布后的某个时间获得。

  3. 同时读者反复尝试获取,在作者发布之前也会失败。

  4. 读者获得。

  5. 请注意这4个操作必须按此顺序进行。写入mItem保证在23之间,并且必须在4之后进行阅读。结合平铺这两个线程仍保留此属性意味着程序正常。