标准原子同步和非原子变量:不是旧数据吗?

3
我知道这段代码是正确的(除了没有执行delete)。
#include <thread>
#include <atomic>
#include <cassert>
#include <string>

std::atomic<std::string*> ptr;
int data;

void producer()
{
    std::string* p  = new std::string("Hello");
    data = 42;
    ptr.store(p, std::memory_order_release);
}

void consumer()
{
    std::string* p2;
    while (!(p2 = ptr.load(std::memory_order_acquire)))
        ;
    assert(*p2 == "Hello"); // never fires
    assert(data == 42); // never fires
}

int main()
{
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join(); t2.join();
}

然而,我想知道为什么在消费者线程中数据不能是旧数据。这是因为acquire操作吗?

1个回答

5

将数据分配给data发生在ptr.store调用之前。通过与原子对象同步,访问data发生在该调用之后。因此,保证访问将看到先前分配的值。


1
编译器必须确保变量的读取观察到在该读取之前发生的写入所分配的最后一个值。如何实现这一点是一个实现细节;通常,它会发出特定于CPU的指令。 - Igor Tandetnik
1
那么这会走多远呢?该函数中的每个变量分配都受到 ptr 提供的屏障的保护吗? - Lightness Races in Orbit
1
简单来说:release => 刷新缓存。acquire => 使缓存失效。消费操作要复杂得多^^ - Antoine Morrier
1
@LightnessRacesinOrbit 从这个角度来考虑。std::string 构造函数对对象的成员进行了一堆赋值操作。我想你不会感到惊讶,一旦 consumer 获取了 ptr 指针,它就会看到 *ptr 的完全初始化状态。这与 data 起作用的原因相同。 - Igor Tandetnik
2
@AntoineMorrier FWIW,将获取-释放语义理解为刷新缓存是有害的。这可能会导致误解,认为这些原子操作必然具有运行时成本。对于像x86这样具有强内存模型的CPU,大多数存储指令已经具有获取-释放语义,而std::atomic + memory_order_release只是为了防止编译时重排序,机器代码没有区别。如果您不想使用std::atomic,则使用带有虚拟asm("":::"memory")的普通变量就足够了。 - llllllllll
显示剩余5条评论

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