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

C++: Reordering atomic store (release) and load (acquire)

我写了下面的代码,它有点像一个写手和一个 reader 的同步队列。不超过 1 reader 和 1 位作者。

作者反复调用maybePublish 设计为无锁。 reader 相反,使用 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不能重新排序。我应该这样做吗?

感谢任何建议!

程序没问题。

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

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

如果我们将发布表示为},将获取表示为{。根据语义,括号内的任何内容都不能向外移动。你的两个线程看起来像

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

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

  3. 同时reader反复尝试获取,同样会失败,直到作者释放。

  4. reader获得。

请注意这 4 个操作必须按此顺序进行。写入 mItem 保证在 23 之间,读取必须发生在 4 之后。结合平铺这两个线程仍然保留此 属性 意味着程序没问题。