Synchronize-With 关系到底是什么?

Yoa*_*ein 3 c++ multithreading memory-model memory-barriers stdatomic

我一直在阅读Jeff Preshing 的这篇关于 Synchronizes-With Relation 的文章,以及cpp 参考中的std::memory_order页面中的“Release-Acquire Ordering”部分,但我不太明白:

似乎标准有某种承诺,但我不明白为什么有必要。让我们以 CPP 参考中的示例为例:

#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();
}
Run Code Online (Sandbox Code Playgroud)

参考文献解释说:

如果线程 A 中的原子存储标记为 memory_order_release,并且线程 B 中来自同一变量的原子加载标记为 memory_order_acquire,则从线程的角度来看,在原子存储之前发生的所有内存写入(非原子和宽松原子) A,在线程 B 中成为可见的副作用。也就是说,一旦原子加载完成,线程 B 就保证可以看到线程 A 写入内存的所有内容。仅当 B 实际上返回 A 存储的值或释放序列中稍后的值时,此承诺才成立。

据我了解,当我们

ptr.store(p, std::memory_order_release)
Run Code Online (Sandbox Code Playgroud)

我们实际上正在做的是告诉编译器和CPU,运行时,使其无法在新值对线程t2可见后可见,data并且指向的内存将不再可见。std::string* pptr

同样,当我们

ptr.load(std::memory_order_acquire)
Run Code Online (Sandbox Code Playgroud)

我们告诉编译器和CPU:使其加载ptr不晚于*p2 和的加载data

所以我不明白我们还有什么进一步的承诺?

ixS*_*Sci 5

ptr.store(p, std::memory_order_release)(L1) 保证了在该特定线程 (T1) 中的该行之前完成的任何操作都将对其他线程可见,只要其他线程ptr以正确的方式读取(在本例中为使用std::memory_order_acquire)。此保证仅适用于这一对,仅此行不保证任何内容。

现在,另一个线程(T2)上有ptr.load(std::memory_order_acquire)(L2),它与另一个线程中的一对一起工作,保证只要它读取 T1 中写入的值,您就可以看到该行之前写入的其他值(在您的情况下)是data)。因为 L1与L2同步,data = 42; 所以发生在 之前 assert(data == 42)

还有一个保证是ptr原子写入和读取,因为它是原子的。该代码中没有其他保证或承诺。

  • 回复:您对@YoavKlein的最后评论是关于其他一些线程在“ptr”的修改顺序中稍后写入“0xdef”而不是“0xabc” - 实际上不会,在ISO C ++中*不会*同步并创建一个发生-在读取器和写入“0xabc”的线程 A 之间的关系之前。不再有连接 A 和 B 的释放序列,除非第三个线程使用像“ptr.exchange(0xdef)”这样的 RMW,而不是“ptr.store(0xdef)”。 (2认同)