代码之家  ›  专栏  ›  技术社区  ›  ron burgundy

获取-释放语义是否跨线程传递?[副本]

  •  1
  • ron burgundy  · 技术社区  · 5 月前

    我最近遇到了关于获得-释放语义传递性的两种看似对立的解释。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

    我理解这本书摘录中的“不能开火”意味着它不会被评估为错误。哪一个是正确的?

    1 回复  |  直到 5 月前
        1
  •  1
  •   HolyBlackCat    5 月前

    首先,对于未来的读者来说,第一个例子意味着获取/释放,尽管它在代码中没有拼写(维基的这一部分讨论了不同的顺序如何影响这个片段)。

    是的,维基似乎错了。虽然“同步”本身是不可传递的(它狭义地指一个线程获取读取另一个线程发布的内容),但这并不重要,因为“发生在之前”(由同步引起)是可传递的。(我在这里混淆了“{线程间,强烈地,简单地}发生在之前”,参见 this .)