我最近遇到了关于获得-释放语义传递性的两种看似对立的解释。A.Williams的《并发在行动中》(见下文)第160页中的“与获取-发布顺序的传递同步”一节与GCC关于内存同步模式的维基(也见下文)直接矛盾。在书中,它基本上说线程同步可以在3个线程之间传递,即使第三个线程没有直接与第一个线程的变量同步。gnu.org的网站则相反。
在gnu.org的GCC Wiki中,“总体摘要”部分给出了以下示例和解释。
-Thread 1- -Thread 2- -Thread 3-
y.store (20); if (x.load() == 10) { if (y.load() == 10)
x.store (10); assert (y.load() == 20) assert (x.load() == 10)
y.store (10)
}
释放/获取模式只需要同步所涉及的两个线程。这意味着同步值不能与其他线程交换。线程2中的断言必须仍然为真,因为线程1和2与x.load()同步。线程3不参与此同步,因此当线程2和3与y.load()同步时,线程3的断言可能会失败。线程1和3之间没有同步,因此不能为其中的“x”假设任何值。
该描述是关于上述代码的获取/发布修改。这一修改涉及所有
.store
调用被替换为具有释放语义的存储,所有
.load
调用被替换为具有获取语义的负载。
来源:
https://gcc.gnu.org/wiki/Atomic/GCCMM/AtomicSync
在Concurrency in Action的第159-160页上,有一个例子说明,即使第三个线程没有直接与第一个线程的变量同步,线程同步也可以在3个线程之间传递。
std::atomic<int> data[1];
std::atomic<bool> sync1(false),sync2(false);
void thread_1()
{
data[0].store(42,std::memory_order_relaxed);
sync1.store(true,std::memory_order_release); // Set sync 1
}
void thread_2()
{
while(!sync1.load(std::memory_order_acquire)) // Loop until sync1 is set
sync2.store(true,std::memory_order_release); // Set sync2
}
void thread_3()
{
while(!sync2.load(std::memory_order_acquire)); // Loop until sync2 is set
assert(data[0].load(std::memory_order_relaxed)==42);
}
获取释放顺序可用于跨多个线程同步数据,即使中间线程没有接触到数据。
…由于happen-before的传递性,您可以将其全部链接在一起:存储到数据发生在存储到sync1之前,这发生在从sync1加载之前,这出现在存储到sync2之前,这在从sync2加载之前,发生在从数据加载之前。因此,thread_1中的数据存储发生在thread_3中的数据加载之前,断言无法触发。
来源:
https://beefnoodles.cc/assets/book/C++%20Concurrency%20in%20Action.pdf
我理解这本书摘录中的“不能开火”意味着它不会被评估为错误。哪一个是正确的?