使用memory_order_relaxed存储memory_order_acquire进行加载

时间:2018-02-10 11:26:04

标签: c++ multithreading c++11 memory concurrency

我有与以下代码相关的问题

#include <atomic>
#include <thread>
#include <assert.h>

std::atomic<bool> x, y;
std::atomic<int> z;

void write_x_then_y()
{
    x.store(true, std::memory_order_relaxed);
    y.store(true, std::memory_order_relaxed);
}

void read_y_then_x()
{
    while (!y.load(std::memory_order_acquire));
    if (x.load(std::memory_order_acquire))
        ++z;
}

int main()
{
    x = false;
    y = false;
    z = 0;
    std::thread a(write_x_then_y);
    std::thread b(read_y_then_x);
    a.join();
    b.join();
    assert(z.load() != 0);
}

我能确定assert(z.load()!= 0)总是假的吗? 我认为x.store和y.store没有在数据提供程序线程中重新排序(这是真的吗?)。 出于这个原因,我认为如果加载由x和y存储的值的线程使用memory_order_acquire,则从执行存储操作符的核心的高速缓存中获取x和y的实际值。

2 个答案:

答案 0 :(得分:3)

我认为断言可能会失败。 std::memory_order_relaxed允许编译器对write_x_then_y内的存储重新排序。 (例如,如果它认为它会因任何原因更快。)因此它可能会在y之前写x。整个read_y_then_x可能发生在这两次写入之间,因此它会y truexfalse并且不会增加z }}

答案 1 :(得分:1)

虽然michalsrb已经回答了,但我正在添加我的答案,因为他以“我认为”开始;)。

C ++内存模型允许断言失败。

以下是一些注释:

  • 使用#include <cassert>;标准标题不会以.h。
  • 结尾
  • atomic<bool>atomic<int>极有可能(无锁定)POD;通过在全局命名空间中定义它们,它们将使用全零的图像进行初始化;也就是说,即使在到达main之前,它们也将分别具有值false0。因此,在主要开头的三个任务没有效果。另一方面,通常(例如,当原子变量在堆栈上时)显式初始化和赋值之间存在差异:初始初始化是非原子的。赋值变为具有内存顺序seq_cst的存储。因此,更好的风格是(仍然)使用初始化列表(使用= false等会做同样的事情):

    std::atomic<bool> x{false};
    std::atomic<bool> y{false};
    std::atomic<int> z{0};
    
  • std::memory_order_acquire仅在读取使用store memory_order_release(包括释放和获取的memory_order_seq_cst)写入的值时才会导致同步。但是,由于您在不同的线程中没有store memory_order_release,因此肯定不会发生任何同步。 main中的初始化是seq_cst,但这是在创建线程b之前完成的,因此已经存在同步(nl。还 - 同步 - 使用,这非常类似于线程间的Sequenced-Before关系)。因此,使用std::memory_order_relaxed而不是std::memory_order_acquire将执行相同的操作并明确使用memory_order_acquire似乎有点奇怪。

那么,因为线程a和b之间没有同步,所以在两个线程看到x和y的变化的顺序之间没有同步,并且线程b在看到x变为真之前可以看到y变为真。

不要试图通过编译器重新排序或硬件管道或任何东西来理解这一点;这是抽象的C ++内存模型“计算机”,它独立于您可能使用的任何实现(编译器)或硬件。事实上,允许进行这种重新排序。考虑到这一点,线程b可以完成并加入,使z仍为其值0。

如果将程序更改为:

,看看会发生什么可能是有益的
void write_x_then_y()
{
    x.store(true, std::memory_order_relaxed);
    y.store(true, std::memory_order_release); // RELEASE HERE
}

void read_y_then_x()
{
    while (!y.load(std::memory_order_acquire));
    if (x.load(std::memory_order_relaxed))
      ++z;
}

线程b仍会挂在while上,直到它为y读取值true。因此,它读取线程1使用存储memory_order_release写入的值!请注意,y的加载仍然使用memory_order_acquire完成。现在进行同步:在我们读取的存储/释放之前写入任何内存位置(也不是y)的所有内容都将在读取后执行读取/获取的线程中可见。换句话说,现在线程a的x.store(true, std::memory_order_relaxed);在执行x的加载时将在线程b中可见;断言永远不会失败。