理解C++内存模型:不同运行时的不同值

4
以下代码有什么问题?我期望看到 consumer1 和 consumer2 输出 10,但有时会看到 -1。
#include <thread>   
#include <atomic>
#include <cassert>
#include <string>

std::atomic<int> global;
void producer()
{
   global.store(10, std::memory_order_release);
}

void consumer1()
{
   int a = global.load(std::memory_order_acquire);
   printf("a in consumer1 %d\n", a);
}

void consumer2()
{
   int a = global.load(std::memory_order_acquire);
   printf("a in consumer2 %d\n", a);
}

int main()
{
    global.store(-1, std::memory_order_seq_cst);
    std::thread t1(producer);
    std::thread t2(consumer1);
    std::thread t3(consumer2);
    t1.join(); t2.join(); t3.join();
}

我看到了以下内容:

a在consumer1中为10 a在consumer2中为10 以及 a在consumer1中为-1 a在consumer2中为10

如果我理解正确,使用memory_order_acquire的线程总是与使用memory_order_release的线程同步。我错了吗? 我在x86-64位机器上运行。我使用以下编译方式: g++ file.cpp -pthread -std=c++11


通过你的示例,我从未得到过-1,总是得到“consumer1中的a为10,consumer2中的a为10”。你能加一些延迟以获得你发布的“错误”结果吗? - Adrian Maire
运行多次。我至少运行了20次,其中一次运行显示为-1。 - username_4567
我已经完成了100个测试。 - Adrian Maire
2
“同步”意味着如果global被生产者更新,消费者将看到更新。可能通过强制缓存更新或其他必要的方式实现。这并不意味着如果消费者超前于生产者运行,它会停止并等待。 - Bo Persson
如果你想让任务A在任务B之前严格完成,最简单的方法是在单个线程上执行它们。让线程1运行到完成,然后再启动线程2继续计算只会效率低下。 - MSalters
显示剩余6条评论
2个回答

4

原子变量有一个非常好的特点,读取的值是先前写入的值。并且使用释放/获取语义,它甚至是最后写入的

在这种情况下,您有2次写入和2次读取。只有-1的写入与读取有序,10的写入不是有序的。因此任何一个值都可能是最后写入的。保证您将读取-1或10而不是垃圾。


只有-1的写入在读取之前被排序,10的写入没有被排序。我不明白"Sequence-before"这个术语是什么意思。 - username_4567
@username_4567:这就是C++11在多线程程序中定义执行顺序的方式。如果两个操作相对于彼此有序,那么先被排序的操作肯定会先发生。未排序的操作可以以任何顺序发生,可能同时进行。 - MSalters

3
如果您在

之前添加一个睡眠函数。
global.store(10, std::memory_order_release);

那么您可以一直观察到一个 -1。

关键是std::memory_order不是类似于信号量的同步方式,而是一个更加微妙的问题。请参见cppreference


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接