<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN">
    <title>whx&#x27;s Blog - 并发</title>
    <subtitle>记录生活</subtitle>
    <link rel="self" type="application/atom+xml" href="https://whx828.com/tags/bing-fa/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://whx828.com"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2023-04-03T00:00:00+00:00</updated>
    <id>https://whx828.com/tags/bing-fa/atom.xml</id>
    <entry xml:lang="zh-CN">
        <title>从 Happened-before 说起（2）</title>
        <published>2023-04-03T00:00:00+00:00</published>
        <updated>2023-04-03T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://whx828.com/posts/happened-before-2/"/>
        <id>https://whx828.com/posts/happened-before-2/</id>
        
        <content type="html" xml:base="https://whx828.com/posts/happened-before-2/">&lt;p&gt;书接上文，继续讨论那个让人疑惑的问题。&lt;br &#x2F;&gt;
为了方便讨论，我把代码挪过来：&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D3C6AA; background-color: #2D353B;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;static&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; X&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; AtomicI32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; AtomicI32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D699B6;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;static&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; Y&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; AtomicI32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; AtomicI32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D699B6;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E67E80;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt; a&lt;&#x2F;span&gt;&lt;span&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt;    X&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt;store&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D699B6;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; Relaxed&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;span style=&quot;color: #859289;font-style: italic;&quot;&gt;    &#x2F;&#x2F; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt;    Y&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt;store&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D699B6;&quot;&gt;20&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; Relaxed&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;span style=&quot;color: #859289;font-style: italic;&quot;&gt;    &#x2F;&#x2F; 2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E67E80;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt; b&lt;&#x2F;span&gt;&lt;span&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span&gt; y&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; Y&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt;load&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt;Relaxed&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;span style=&quot;color: #859289;font-style: italic;&quot;&gt; &#x2F;&#x2F; 3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; X&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt;load&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt;Relaxed&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;span style=&quot;color: #859289;font-style: italic;&quot;&gt; &#x2F;&#x2F; 4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt;    println!&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DBBC7F;&quot;&gt;x&lt;&#x2F;span&gt;&lt;span&gt;} {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DBBC7F;&quot;&gt;y&lt;&#x2F;span&gt;&lt;span&gt;}&amp;quot;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;More interestingly, the output can also be 0 20, even though there is no possible globally consistent order of the four operations that would result in this outcome. When 3 is executed, there is no happens-before relationship with 2, which means it could load either 0 or 20. When 4 is executed, there is no happens-before relationship with 1, which means it could load either 0 or 10. Given this, the output 0 20 is a valid outcome.&lt;br &#x2F;&gt;
更有趣的是，输出也可以是 0 20，即使没有可能导致此结果的四个操作的全局一致顺序。当 3 执行时，与 2 没有 happens-before 关系，也就是说它可以加载 0 或 20。当 4 执行时，与 1 没有 happens-before 关系，这意味着它可以加载 0 或 10。鉴于此，输出 0 20 是一个有效的结果。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;想要彻底搞明白，还得从概念的细节下手：&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;…the basic happens-before rule is that everything that happens within the same thread happens in order. In this case: 1 happens-before 2, and 3 happens-before 4. Since we use relaxed memory ordering, there are no other happens-before relationships in our example.&lt;br &#x2F;&gt;
……基本的 happens-before 规则是同一线程内发生的所有事情都按顺序发生。在这种情况下：1 happens-before 2，3 happens-before 4。由于我们使用 &lt;code&gt;Relaxed&lt;&#x2F;code&gt; 内存排序，因此在我们的示例中没有其他 happens-before 关系。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;那到底什么是 happened-before？这里是维基百科给的定义：&lt;&#x2F;p&gt;
&lt;p&gt;In &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; title=&quot;Computer science&quot; href=&quot;https:&#x2F;&#x2F;www.wikiwand.com&#x2F;en&#x2F;Computer_science&quot;&gt;computer science&lt;&#x2F;a&gt;, the &lt;strong&gt;happened-before&lt;&#x2F;strong&gt; &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; title=&quot;Binary relation&quot; href=&quot;https:&#x2F;&#x2F;www.wikiwand.com&#x2F;en&#x2F;Binary_relation&quot;&gt;relation&lt;&#x2F;a&gt; (denoted: →) is a relation between the result of two events, such that if one event should happen before another event, the result must reflect that, even if those events are in reality executed out of order (usually to optimize program flow). This involves &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; title=&quot;Partially ordered set&quot; href=&quot;https:&#x2F;&#x2F;www.wikiwand.com&#x2F;en&#x2F;Partially_ordered_set&quot;&gt;ordering&lt;&#x2F;a&gt; events based on the potential &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; title=&quot;Causal relationships&quot; href=&quot;https:&#x2F;&#x2F;www.wikiwand.com&#x2F;en&#x2F;Causal_relationships&quot;&gt;causal relationship&lt;&#x2F;a&gt; of pairs of events in a concurrent system, especially &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; title=&quot;Asynchronous communication&quot; href=&quot;https:&#x2F;&#x2F;www.wikiwand.com&#x2F;en&#x2F;Asynchronous_communication&quot;&gt;asynchronous&lt;&#x2F;a&gt; &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; title=&quot;Distributed systems&quot; href=&quot;https:&#x2F;&#x2F;www.wikiwand.com&#x2F;en&#x2F;Distributed_systems&quot;&gt;distributed systems&lt;&#x2F;a&gt;. It was formulated by &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; title=&quot;Leslie Lamport&quot; href=&quot;https:&#x2F;&#x2F;www.wikiwand.com&#x2F;en&#x2F;Leslie_Lamport&quot;&gt;Leslie Lamport&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
在计算机科学中，happened-before 关系（表示为：→）是两个事件的结果之间的关系，这样如果一个事件应该在另一个事件之前发生，那么即使这些事件在现实中发生，结果也必须反映出来乱序执行（通常是为了优化程序流程）。这涉及根据并发系统中事件对的潜在因果关系对事件进行排序，尤其是异步分布式系统。它是由 Leslie Lamport 制定的。&lt;&#x2F;p&gt;
&lt;p&gt;The happened-before relation is formally defined as the least &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; title=&quot;Strict partial order&quot; href=&quot;https:&#x2F;&#x2F;www.wikiwand.com&#x2F;en&#x2F;Strict_partial_order&quot;&gt;strict partial order&lt;&#x2F;a&gt; on events such that:&lt;br &#x2F;&gt;
happened-before 关系的正式定义为──事件最不严格的偏序，使得：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;If events a and b occur on the same process, a → b if the occurrence of event a preceded the occurrence of event b.&lt;br &#x2F;&gt;
如果事件 a 和 b 在同一进程上，则 a → b。事件 a 的发生先于事件 b 的发生。&lt;&#x2F;li&gt;
&lt;li&gt;If event a is the sending of a message and event b is the reception of the message sent in event a, a → b.&lt;br &#x2F;&gt;
如果事件 a 是消息的发送者，事件 b 是 a 发送的消息的接收者，则 a → b。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;If two events happen in different isolated processes (that do not exchange messages directly or indirectly via third-party processes), then the two processes are said to be concurrent, that is neither a → b nor b → a is true.&lt;br &#x2F;&gt;
如果两个事件发生在不同的隔离进程中（不直接或通过第三方进程间接交换消息），则称这两个进程是并发的，a → b 和 b → a 都不成立。&lt;&#x2F;p&gt;
&lt;p&gt;If there are other causal relationships between events in a given system, such as between the creation of a process and its first event, these relationships are also added to the definition. For example, in some programming languages such as Java, C, C++ or Rust, a &lt;strong&gt;happens-before&lt;&#x2F;strong&gt; edge exists if memory written to by statement A is visible to statement B, that is, if statement A completes its write before statement B starts its read.&lt;br &#x2F;&gt;
如果给定系统中的事件之间存在其他因果关系，例如进程的创建与此进程第一个事件之间的因果关系，这些关系也会添加到定义中。例如，在某些编程语言中，如 Java、C、C++ 或 Rust，如果语句 A 写入的内存对语句 B 可见，即如果语句 A 在语句 B 开始读取之前完成了写入，则存在 happens-before 关系。&lt;&#x2F;p&gt;
&lt;p&gt;The processes that make up a distributed system have no knowledge of the happened-before relation unless they use a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; title=&quot;Logical clock&quot; href=&quot;https:&#x2F;&#x2F;www.wikiwand.com&#x2F;en&#x2F;Logical_clock&quot;&gt;logical clock&lt;&#x2F;a&gt;, like a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; title=&quot;Lamport clock&quot; href=&quot;https:&#x2F;&#x2F;www.wikiwand.com&#x2F;en&#x2F;Lamport_clock&quot;&gt;Lamport clock&lt;&#x2F;a&gt; or a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; title=&quot;Vector clock&quot; href=&quot;https:&#x2F;&#x2F;www.wikiwand.com&#x2F;en&#x2F;Vector_clock&quot;&gt;vector clock&lt;&#x2F;a&gt;. This allows one to design algorithms for &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; title=&quot;Mutual exclusion&quot; href=&quot;https:&#x2F;&#x2F;www.wikiwand.com&#x2F;en&#x2F;Mutual_exclusion&quot;&gt;mutual exclusion&lt;&#x2F;a&gt;, and tasks like debugging or optimising distributed systems.&lt;br &#x2F;&gt;
构成分布式系统的进程之间没有 happened-before 关系，除非它们使用逻辑时钟，如 Lamport 时钟或矢量时钟。这允许人们设计互斥算法，以及调试或优化分布式系统等任务。&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;维基百科的定义更加明确：happened-before 表征的是一个事件应该在另一个事件之前发生。在这个例子中，我们只能肯定的是：操作 1 happened-before 操作 2，操作 3 happened-before 操作 4，其他什么都无法肯定，即使你看到了程序输出 &lt;code&gt;0 20&lt;&#x2F;code&gt; 也不意味着操作 4 在操作 1 之前发生或者操作 2 在操作 3 之前发生，换句话说，正是因为你使用的是 &lt;code&gt;Relaxed&lt;&#x2F;code&gt; 内存排序，Rust 也就放弃提供跨线程的操作顺序的保证了，而我觉得无法理解是因为我之前认为 &lt;code&gt;0 20&lt;&#x2F;code&gt; 根本就无法发生，但真的不会吗？来看看 GPT-4 的回答：&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;在多线程环境中，使用 &lt;code&gt;Relaxed&lt;&#x2F;code&gt; 内存排序的原子操作不会产生跨线程的 happened-before 关系。这意味着，当线程执行操作 3 时，我们不能确定操作 2 是否已经发生。同样地，当线程执行操作 4 时，我们也不能确定操作 1 是否已经发生。&lt;&#x2F;p&gt;
&lt;p&gt;以下是可能导致输出 0 20 的一种情况：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;线程 a 执行操作 1，将 X 设置为 10。&lt;&#x2F;li&gt;
&lt;li&gt;线程 b 执行操作 3，加载 Y 的值。此时，线程 a 尚未执行操作 2，因此 Y 的值仍然是 0。所以，操作 3 加载的值是 0。&lt;&#x2F;li&gt;
&lt;li&gt;线程 a 执行操作 2，将 Y 设置为 20。&lt;&#x2F;li&gt;
&lt;li&gt;线程 b 执行操作 4，加载 X 的值。此时，线程 a 已经执行了操作 1，但由于没有跨线程的 happens-before 关系，线程 b 仍然可以看到 X 的旧值 0。因此，操作 4 加载的值是 0。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;这就是为什么输出可能是 0 20。在这种情况下，线程 b 观察到的操作顺序实际上是：操作 3、操作 4、操作 1、操作 2。这个例子说明了在使用 &lt;code&gt;Relaxed&lt;&#x2F;code&gt; 内存排序时，不同线程之间的操作顺序是不确定的，可能导致一些看似违反直觉的结果。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;我对这个答案不是很满意，它并未说服我，所以我接着追问：&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;为什么没有跨线程的 happened-before 关系，线程 b 就可以看到 X 的旧值 0 呢？（这也的确是我最核心的疑问）&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;当没有跨线程的 happened-before 关系时，线程 b 可以看到 X 的旧值 0，是因为内存顺序、编译器优化和处理器行为可能导致数据在不同线程之间的可见性有延迟。在我们的例子中，由于使用了 &lt;code&gt;Relaxed&lt;&#x2F;code&gt; 内存排序，编译器和处理器对操作的优化和重排更加自由。这可能导致线程 a 对 X 的写入操作（操作 1）在线程 b 观察到之前存在一定的延迟。以下是一些可能导致这种现象的因素：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;编译器优化：编译器可能会对代码进行优化，以提高执行效率。在 &lt;code&gt;Relaxed&lt;&#x2F;code&gt; 内存排序下，编译器可以自由地重新排序操作，只要它们在单个线程中的顺序保持不变。这种优化可能导致线程 b 在观察到线程 a 对 X 的写入操作之前存在延迟。&lt;&#x2F;li&gt;
&lt;li&gt;处理器缓存：现代处理器通常具有多级缓存，用于加速内存访问。当一个线程对内存位置进行写操作时，这个改动可能首先出现在处理器的缓存中，而不是立即写回到主存。在没有强制 happened-before 关系的情况下，其他线程可能在一段时间内仍然看到旧的值。&lt;&#x2F;li&gt;
&lt;li&gt;处理器指令重排：为了提高执行效率，处理器可能会对指令进行重排。这意味着实际执行的指令顺序可能与代码中的顺序不同。在 &lt;code&gt;Relaxed&lt;&#x2F;code&gt; 内存排序下，指令重排可能导致线程 b 在观察到线程 a 对 X 的写入操作之前存在延迟。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;因此，在没有跨线程的 happened-before 关系的情况下，线程 b 可能看到 X 的旧值 0，是由于编译器优化、处理器缓存和处理器指令重排等因素导致的内存访问延迟。这些因素使得线程之间的操作顺序和数据可见性变得不确定。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;GPT-4 提供了几种可能会发生的场景，成功说服了我，因为真的有可能发生。同时我也对内存排序是如何做到的有了新的理解，这并不完全是 Rust 语言本身能决定的事情，你要考虑延迟、缓存、编译器，甚至还需要硬件、处理器的配合，那么这其中的自由度和复杂度就不是常人能全部考虑到的。&lt;&#x2F;p&gt;
&lt;p&gt;行文至此，再次感觉到编程的复杂，看起来简单的内存排序竟会在背后包含如此复杂的内容，不得不感叹计算机是一个整体，到了一定复杂度以后很难完全抽象出单一元素来完备地解答问题。如此发展下去，恐怕到最后也只有机器能解答机器了吧。&lt;&#x2F;p&gt;
&lt;p&gt;另外 AI 的确不失为一种很棒的学习工具，GPT-4 并不是一次性就给出了上述回答，它前几次的回答有错误，考虑到避免误导，我就没有把错误的对话也放上来，有的时候答案其实并不重要，如果能正确地发问，其实有些时候问题也就解决了。&lt;br &#x2F;&gt;
目前的 GPT 模型其实效果已经相当好了，但依然存在一本正经地胡说行为，因此如果想用它来学习，你最好有能分辨答案是否正确的能力，这又有些悖论了，我如果有这个能力，往往就没那么需要再问了。&lt;&#x2F;p&gt;
&lt;p&gt;从维基百科中可以得知 happened-before 是由 Leslie Lamport 这位分布式大牛提出的，happened-before 是为了一个更大的问题所做的铺垫，这个问题就是分布式系统中的时间。分布式系统中相当多的概念和算法都需要依靠 happened-before 这个偏序关系，说它是构成分布式世界的基石一点也不为过，详细的展开则要去读《Time, Clocks, and the Ordering of Events in a Distributed System》这篇论文才能知道了。&lt;&#x2F;p&gt;
&lt;p&gt;其实 happened-before 还有许多部分可以展开，不过鄙人才疏学浅，目前只掌握了这些，或许以后还会写点自己的心得体会吧，敬请期待。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="zh-CN">
        <title>从 Happened-before 说起（1）</title>
        <published>2023-03-22T00:00:00+00:00</published>
        <updated>2023-03-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://whx828.com/posts/happened-before-1/"/>
        <id>https://whx828.com/posts/happened-before-1/</id>
        
        <content type="html" xml:base="https://whx828.com/posts/happened-before-1/">&lt;blockquote&gt;
&lt;p&gt;本文大部分是背景介绍，本来想一气呵成，但发现要讲的东西有点多，遂分成了两篇来写。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;m-ou.se&#x2F;&quot;&gt;Mara Bos&lt;&#x2F;a&gt; 关于并发主题的新书 &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;marabos.nl&#x2F;atomics&#x2F;&quot;&gt;Rust Atomics and Locks&lt;&#x2F;a&gt; 最近可以免费在线阅读了，这位 Rust Library team 的 Leader 自不必多说，对 Rust 的理解（贡献）非是常人可比，而这次 Mara 博士这次的慷慨更是让手头常年拮据学生党（鄙人）直呼老铁 666！&lt;del&gt;耶，是官方的白嫖！&lt;&#x2F;del&gt;&lt;&#x2F;p&gt;
&lt;p&gt;玩笑差不多了，说正事。&lt;br &#x2F;&gt;
尽管鄙人的英语是中国人水平，但为了学点 Rust，困难可以克服（感谢谷歌翻译+DeepL+ChatGPT），这本书不长，但想要读透，真正领会作者的思想，要花好多功夫。在一众翻译软件的帮助下，我简单阅读了这本书的前几章内容，其中第一个令我印象深刻的内容当属第 3 章中提到的也是我们今天的主题：Happened-before 关系。&lt;&#x2F;p&gt;
&lt;p&gt;要讨论 Happened-before 关系，说来话长，让我们保持耐心，先从简单的计算机历史说起。&lt;br &#x2F;&gt;
最初的计算机工作非常简单，只要把一些简单繁琐的计算任务搞定即可，在真空管和穿孔打卡的时代，程序员是把程序写到纸上，你没看错，是纸，然后在纸上穿孔，如此反复操作，直到穿成厚厚的一摞，再把这一摞交给计算机，计算机计算结果打印出来，然后程序员拿到打印结果。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;这就是最开始的 IO。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;用今天的眼光看那时的程序员也太不容易了，纸上编程确实有助于学习编码和思考代码逻辑，但如果真要把干活的程序写在纸上再去穿孔才能执行，这种繁琐、浪费是现在的程序员所无法想象的（谁愿意天天穿孔），你可能会问：如果我就运行自己的程序行不行，答案是不行！在当时计算机的资源是相当昂贵的，没有那么多机器让你一个人玩，就像买东西太贵选择拼单一样，你写程序也要和人拼计算机，所谓批处理就是这么一回事，名字起得挺高深，实际上背后一把辛酸泪。&lt;&#x2F;p&gt;
&lt;p&gt;这样的 IO 用脚趾头想都能想到肯定会和 CPU 的速度存在巨大差异，CPU 的计算时间非常少而等待 IO 结束的时间却很多。所以为了 &lt;del&gt;让 CPU 更累&lt;&#x2F;del&gt; 更合理地利用 CPU 计算资源，我们就得想个办法，看能不能「偷」点时间出来。后来不知道哪位天使大哥&#x2F;大姐想到了一个鬼点子：我们可以把内存划分为多个部分（日子宽裕了，内存也比以前大了，可以让更多程序用了），不同的程序可以使用各自的内存部分互不干涉，这些单独的程序就是所谓的进程。然后我们给 CPU 再加上一个超能力：跨进程切换执行。这样就有戏法可以玩了：当某个进程进行磁盘 IO 的时候，CPU 就可以切换到另外一个进程执行（闲着也是闲着，不如干点别的活），这样就 &lt;del&gt;让 CPU 更累&lt;&#x2F;del&gt; 偷出了本来可以摸鱼（等待 IO）的时间来干活（运行其他进程），我们的目的达到了！（能力越大，责任就越大嘛~）&lt;&#x2F;p&gt;
&lt;p&gt;怎么给 CPU 加上跨进程切换执行的超能力呢？请看三板斧：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;CPU 时间片：现在我们有很多进程，所以对于每个进程如何执行，需要有个规定──每一次切换进程后，CPU 只会工作一段时间，然后又切换到另外一个进程，再执行一段时间。我们把 CPU 切换进程的这个时间段叫做“时间片”。&lt;&#x2F;li&gt;
&lt;li&gt;程序计数器：CPU 经过一圈切换后还会切换回最开始的进程继续执行（对每个进程都是如此），所以它需要知道进程已经执行到了哪一步才好接着执行，程序计数器应运而生，它会记住进程执行到的位置。有了这个就可以继续执行了。&lt;&#x2F;li&gt;
&lt;li&gt;栈指针：程序计数器解决了记忆程序（代码）执行到的位置，但是每个进程还持有操作系统分配给它的内存资源，这玩意对每个进程是独立的，因此也需要记录，所以我们用栈指针来记录操作系统给它的内存资源地址。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;这三板斧下去，CPU 再也不好摸鱼了，比起以前，那效率是高多了，但是人类对于极致的追求是无止境的，大家慢慢发现切换进程这个操作本身居然挺费时间，如果每个进程都有很多 IO，那么 CPU 的时间都被切换进程浪费掉了，然后还什么都没做，这不是又让 CPU 闲着了吗？不行，必须要解决！&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;这只是一个缺点，相关的缺点还有很多，不一一列举了。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;进程切换成本这么高，一部分的原因是进程太「重」了，每切换一次上面的三板斧都得顾及到，那么让它变得小一点不就好了吗？这就是线程：我们让一个进程创建很多个线程，然后让线程来做以前进程的工作，现在 CPU 会在线程而不是进程之间来回切换，由于线程更小，时间片也分得更小，以至于多个线程之间切换执行成本也小了，同时 CPU 等待 IO 的几率也更加小了。一举多得。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;线程版本的三板斧：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;程序计数器，表示当前线程执行指令的位置。&lt;&#x2F;li&gt;
&lt;li&gt;保存变量的寄存器。&lt;&#x2F;li&gt;
&lt;li&gt;栈。每个线程的栈记录了函数调用的记录，并反映了当前线程的执行点。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;技术领域有一句名言：没有银弹。意思是不管什么问题，没有万能解决方案。多线程也不是没缺点，对程序员来说最直观的感受就是：多线程编程更难。为什么？因为在多线程编程的场景下，你会经常遇到「多个线程同时读取和修改同一个变量」的问题，而在 Rust 中，我们认为“这通常会导致未定义的行为（UB）”。&lt;&#x2F;p&gt;
&lt;p&gt;为什么“多个线程同时读取和修改同一个变量”会是个问题？这不是效率更高了吗？请看下面的伪代码：&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D3C6AA; background-color: #2D353B;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;data_race() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  global x = 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  thread1 = thread {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    for i in 0..100 {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      x += 1;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  thread2 = thread {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    for i in 0..100 {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      x += 1;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  t1 = thread1.spawn();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  t2 = thread2.spawn();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  t1.join();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  t2.join();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  return x;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;之所以写伪代码是因为用 Rust 想写个有问题的程序着实不容易。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;伪代码说明（虽然我希望这是不言自明的）：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;global x = 0&lt;&#x2F;code&gt; 表示 &lt;code&gt;x&lt;&#x2F;code&gt; 是个全局变量，每个线程都能看到并修改它的值&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;thread1 =  thread { for i in 0..100 { x += 1; } };&lt;&#x2F;code&gt; 表示 &lt;code&gt;thread1&lt;&#x2F;code&gt; 对 &lt;code&gt;x&lt;&#x2F;code&gt; 进行 100 次加 1 操作，这是 &lt;code&gt;thread1&lt;&#x2F;code&gt; 的定义&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;t1 = thread1.spawn();&lt;&#x2F;code&gt; 则是启动 &lt;code&gt;thread1&lt;&#x2F;code&gt;，&lt;code&gt;thread1&lt;&#x2F;code&gt; 自此开始执行&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;thread1.join();&lt;&#x2F;code&gt; 表示当前线程（主线程）将一直等待 &lt;code&gt;thread1&lt;&#x2F;code&gt; 执行结束&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;thread2&lt;&#x2F;code&gt; 上述一切同理&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;请猜想下，这段程序执行到 &lt;code&gt;return x;&lt;&#x2F;code&gt; 时，&lt;code&gt;x&lt;&#x2F;code&gt; 的值会是多少？&lt;&#x2F;p&gt;
&lt;p&gt;答案是：只有天知道，理论上在 100-200 之间的任何整数值都有可能。&lt;&#x2F;p&gt;
&lt;p&gt;你可能会问：&lt;code&gt;x&lt;&#x2F;code&gt; 不是 &lt;code&gt;200&lt;&#x2F;code&gt; 吗？&lt;br &#x2F;&gt;
&lt;code&gt;x&lt;&#x2F;code&gt; 不一定会是 &lt;code&gt;200&lt;&#x2F;code&gt;，这就是多线程编程的吊诡之处，很反直觉。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;更反直觉的还在后面。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;试想下面的过程：&lt;br &#x2F;&gt;
（1）CPU 时间片先分给 &lt;code&gt;thread1&lt;&#x2F;code&gt; 执行，假设代表 &lt;code&gt;x&lt;&#x2F;code&gt; 这个变量的寄存器在 &lt;code&gt;thread1&lt;&#x2F;code&gt; 中执行累加到了 50，此时恰巧时间片用完了，而存放在寄存器中的值 50 还没来及写入实际的物理内存。&lt;br &#x2F;&gt;
（2）因为时间片用完了，CPU 现在分配给 &lt;code&gt;thread2&lt;&#x2F;code&gt;，由于 &lt;code&gt;thread1&lt;&#x2F;code&gt; 算出来的值并没有写回内存，所以实际上此时 &lt;code&gt;thread2&lt;&#x2F;code&gt; 还是从 &lt;code&gt;x = 0&lt;&#x2F;code&gt; 开始进行加 1 操作，这次我们假设分给它的时间片够进行 60 次加 1 操作，之后 &lt;code&gt;thread2&lt;&#x2F;code&gt; 将得到的 &lt;code&gt;x = 60&lt;&#x2F;code&gt; 写入实际的物理内存。&lt;br &#x2F;&gt;
（3）时间片再度分配给 &lt;code&gt;thread1&lt;&#x2F;code&gt;，&lt;code&gt;thread1&lt;&#x2F;code&gt; 开始执行它在上一个时间片结束时没有执行完的工作，将 &lt;code&gt;x = 50&lt;&#x2F;code&gt; 写入实际的物理内存，计算机严格按照代码执行指令，但此时问题出现：&lt;code&gt;thread1&lt;&#x2F;code&gt; 计算并写入的 &lt;code&gt;x = 50&lt;&#x2F;code&gt; 会把 &lt;code&gt;thread2&lt;&#x2F;code&gt; 计算并写入的 &lt;code&gt;x = 60&lt;&#x2F;code&gt; 覆盖掉。而每次执行程序，上述情况的发生总不会完全一致，比如时间片的大小就不是能完全确定的，相当于每次都有微调，这就是多次执行相同的代码得到的结果却完全不一致的原因。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;其实真实的过程比这复杂得多，这里进行了简化以方便讨论概念。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;如何解决这样的问题？先来看问题出在哪里。我们可以发现上面程序过程中的关键点是：每个线程的 &lt;strong&gt;计算 &lt;code&gt;x&lt;&#x2F;code&gt; 的值&lt;&#x2F;strong&gt; 和 &lt;strong&gt;把算好的值存入物理内存&lt;&#x2F;strong&gt; 这两个操作之间是 &lt;strong&gt;不连续的&lt;&#x2F;strong&gt;，这样就给了其他线程进来搞破坏的可趁之机。&lt;br &#x2F;&gt;
如果在单个线程中，计算值和把算好的值存入内存这两个操作连续进行是很自然的，自然到你根本不会意识到它们还可以不连续，这是你编程时的默认心智模型，如果这一条都不成立大家也就不用编码了。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;其实这里面还有指令重排的事，别以为你「看到」的就是「真相」。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;但是在多线程环境中，这就成为了问题，为了灵活和性能，多线程编程的自由度更高。如果多线程编程要强行规定线程间的执行顺序、线程内的指令执行顺序，那样还有意义吗？还不如继续优化多进程。&lt;&#x2F;p&gt;
&lt;p&gt;好，我们现在遇到了一个“既要…还要…”的问题：我们既要留住多线程的灵活，又要让它在某些时候不那么灵活，有没有两全法呢？有，原子操作！&lt;&#x2F;p&gt;
&lt;p&gt;原子操作是什么呢？我认为最通俗易懂的说法是：要么不做，要么全做。不过我的总结不够规范，咱们还是来看专业人士（Mara Bos 博士）是怎么表述这个概念的：&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;The word atomic comes from the Greek word ἄτομος, meaning indivisible, something that cannot be cut into smaller pieces. In computer science, it is used to describe an operation that is indivisible: it is either fully completed, or it didn’t happen yet.&lt;br &#x2F;&gt;
原子这个词来自希腊语 ἄτομος，意思是不可分割的，不能被切割成更小的部分。在计算机科学中，它被用来描述一个不可分割的操作：它要么完全完成，要么还没有发生。&lt;br &#x2F;&gt;
However, atomic operations do allow for different threads to safely read and modify the same variable. Since such an operation is indivisible, it either happens completely before or completely after another operation, avoiding undefined behavior.&lt;br &#x2F;&gt;
原子操作允许不同的线程安全地读取和修改同一个变量。由于原子操作是不可分割的，它要么完全发生在另一个操作之前，要么完全发生在另一个操作之后，这样就避免了 UB。&lt;&#x2F;p&gt;
&lt;p&gt;Atomic operations are the main building block for anything involving multiple threads. All the other concurrency primitives, such as mutexes and condition variables, are implemented using atomic operations.&lt;br &#x2F;&gt;
原子操作是涉及多线程的任何事物的主要构建块。所有其他并发原语，例如互斥量和条件变量，都是使用原子操作实现的。&lt;br &#x2F;&gt;
In Rust, atomic operations are available as methods on the standard atomic types that live in &lt;code&gt;std::sync::atomic&lt;&#x2F;code&gt;. They all have names starting with &lt;code&gt;Atomic&lt;&#x2F;code&gt;, such as &lt;code&gt;AtomicI32&lt;&#x2F;code&gt; or &lt;code&gt;AtomicUsize&lt;&#x2F;code&gt;. Which ones are available depends on the hardware architecture and sometimes operating system, but almost all platforms provide at least all atomic types up to the size of a pointer.&lt;br &#x2F;&gt;
在 Rust 中，原子操作可以作为存在于 &lt;code&gt;std::sync::atomic&lt;&#x2F;code&gt; 中的标准原子类型的方法。它们的名称都以 &lt;code&gt;Atomic&lt;&#x2F;code&gt; 开头，例如 &lt;code&gt;AtomicI32&lt;&#x2F;code&gt; 或 &lt;code&gt;AtomicUsize&lt;&#x2F;code&gt;。哪些可用取决于硬件架构，有时取决于操作系统，但几乎所有平台都至少提供所有原子类型，最大为指针大小。&lt;br &#x2F;&gt;
Unlike most types, they allow modification through a shared reference (e.g., &lt;code&gt;&amp;amp;AtomicU8&lt;&#x2F;code&gt;). This is possible thanks to interior mutability, as discussed in “Interior Mutability” in Chapter 1.&lt;br &#x2F;&gt;
与大多数类型不同，它们允许通过共享引用进行修改（例如 &lt;code&gt;&amp;amp;AtomicU8&lt;&#x2F;code&gt;）。这要归功于内部可变性，正如第 1 章“内部可变性”中所讨论的那样。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;我们可以看到“原子操作是不可分割的”，放在我们的例子中这也就意味着我们可以通过原子操作，让线程的 &lt;strong&gt;计算 &lt;code&gt;x&lt;&#x2F;code&gt; 的值&lt;&#x2F;strong&gt; 和 &lt;strong&gt;把算好的值存入物理内存&lt;&#x2F;strong&gt; 之间是 &lt;strong&gt;不可分割的&lt;&#x2F;strong&gt;，那么也就不会出现前文中所述的错误，&lt;code&gt;x&lt;&#x2F;code&gt; 就会是 &lt;code&gt;200&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;Rust 是一门标榜“安全”的语言，既然多线程编程中会有“不安全”的场景出现，那么 Rust 肯定要考虑如何解决，不过相比 The book 中其他主题的 Rust 安全特色（所有权、借用检查等），我读到 Rust Atomics and Locks 后面只觉得在多线程编程方面，Rust 还是很明显地抄了不少别人（C++）的东西，例如下面 Mara Bos 博士话锋一转，开始讨论起了另一个概念：&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;But, before we can dive into the different atomic operations, we briefly need to touch upon a concept called memory ordering:&lt;br &#x2F;&gt;
但是，在我们深入研究不同的原子操作之前，我们需要简单地谈谈一个叫做内存排序的概念：&lt;br &#x2F;&gt;
Every atomic operation takes an argument of type &lt;code&gt;std::sync::atomic::Ordering&lt;&#x2F;code&gt;, which determines what guarantees we get about the relative ordering of operations. The simplest variant with the fewest guarantees is &lt;code&gt;Relaxed&lt;&#x2F;code&gt;. &lt;code&gt;Relaxed&lt;&#x2F;code&gt; still guarantees consistency on a single atomic variable, but does not promise anything about the relative order of operations between different variables.&lt;br &#x2F;&gt;
每个原子操作都有一个类型为 &lt;code&gt;std::sync::atomic::Ordering&lt;&#x2F;code&gt; 的参数，它决定了我们对操作的相对顺序有什么「保证」。而「保证」最少的、最简单的变体是 &lt;code&gt;Relaxed&lt;&#x2F;code&gt;。&lt;code&gt;Relaxed&lt;&#x2F;code&gt; 仍然保证单个原子变量的一致性，但不保证不同变量之间的相对操作顺序。&lt;br &#x2F;&gt;
What this means is that two threads might see operations on different variables happen in a different order. For example, if one thread writes to one variable first and then to a second variable very quickly afterwards, another thread might see that happen in the opposite order.&lt;br &#x2F;&gt;
这意味着两个线程可能会看到对不同变量的操作以不同的顺序发生。例如，如果一个线程首先写入一个变量，然后很快写入第二个变量，则另一个线程可能会看到以相反顺序发生的情况。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;我之前说有更反直觉的东西，这不现在看到了？为什么第二个线程可能看到以相反顺序发生的情况？&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Processors and compilers perform all sorts of tricks to make your programs run as fast as possible. A processor might determine that two particular consecutive instructions in your program will not affect each other, and execute them out of order, if that is faster, for example. While one instruction is briefly blocked on fetching some data from main memory, several of the following instructions might be executed and finished before the first instruction finishes, as long as that wouldn’t change the behavior of your program. Similarly, a compiler might decide to reorder or rewrite parts of your program if it has reason to believe it might result in faster execution. But, again, only if that wouldn’t change the behavior of your program.&lt;br &#x2F;&gt;
处理器和编译器执行各种技巧以使您的程序运行得尽可能快。处理器可能会确定您程序中的两个特定的连续指令不会相互影响，并乱序执行它们（例如，如果这样更快的话）。虽然一条指令在从主存中获取一些数据时被短暂阻塞，但在第一条指令完成之前，可能会执行并完成以下几条指令，只要这不会改变程序的行为。同样，如果编译器有理由相信它可能会导致更快的执行，它可能会决定重新排序或重写程序的某些部分。但是，同样，前提是这不会改变您的程序的行为。&lt;&#x2F;p&gt;
&lt;p&gt;Let’s take a look at the following function as an example:&lt;br &#x2F;&gt;
让我们以下面的函数为例：&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D3C6AA; background-color: #2D353B;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E67E80;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt; f&lt;&#x2F;span&gt;&lt;span&gt;(a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;: &amp;amp;mut&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; i32&lt;&#x2F;span&gt;&lt;span&gt;, b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;: &amp;amp;mut&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; i32&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;    *&lt;&#x2F;span&gt;&lt;span&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D699B6;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;    *&lt;&#x2F;span&gt;&lt;span&gt;b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D699B6;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;    *&lt;&#x2F;span&gt;&lt;span&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D699B6;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Here, the compiler will most certainly understand that the order of these operations does not matter, since nothing happens between these three addition operations that depends on the value of &lt;code&gt;*a&lt;&#x2F;code&gt; or &lt;code&gt;*b&lt;&#x2F;code&gt;. (Assuming overflow checking is disabled.) Because of that, it might reorder the second and third operations, and then merge the first two into a single addition:&lt;br &#x2F;&gt;
在这里，编译器肯定会理解这些操作的顺序无关紧要，因为这三个取决于 &lt;code&gt;*a&lt;&#x2F;code&gt; 或 &lt;code&gt;*b&lt;&#x2F;code&gt; 的值的加法操作之间没有任何关联。（假设溢出检查被禁用）因此，它可能会重新排序第二个和第三个操作，然后将前两个合并为一个加法：&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D3C6AA; background-color: #2D353B;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E67E80;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt; f&lt;&#x2F;span&gt;&lt;span&gt;(a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;: &amp;amp;mut&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; i32&lt;&#x2F;span&gt;&lt;span&gt;, b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;: &amp;amp;mut&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; i32&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;    *&lt;&#x2F;span&gt;&lt;span&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D699B6;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;    *&lt;&#x2F;span&gt;&lt;span&gt;b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D699B6;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Later, while executing this function of the optimized compiled program, a processor might for a variety of reasons end up executing the second addition before the first addition, possibly because &lt;code&gt;*b&lt;&#x2F;code&gt; was available in a cache, while &lt;code&gt;*a&lt;&#x2F;code&gt; had to be fetched from the main memory.&lt;br &#x2F;&gt;
稍后，在执行优化编译程序的这个函数时，处理器可能由于各种原因在第一次加法之前结束执行第二次加法，这可能是因为 &lt;code&gt;*b&lt;&#x2F;code&gt; 在缓存中可用，而 &lt;code&gt;*a&lt;&#x2F;code&gt; 必须从主存提取。&lt;br &#x2F;&gt;
Regardless of these optimizations, the result stays the same: &lt;code&gt;*a&lt;&#x2F;code&gt; is incremented by two and &lt;code&gt;*b&lt;&#x2F;code&gt; is incremented by one. The order in which they were incremented is entirely invisible to the rest of your program.&lt;br &#x2F;&gt;
不管这些优化如何，结果都保持不变：&lt;code&gt;*a&lt;&#x2F;code&gt; 增加 2，&lt;code&gt;*b&lt;&#x2F;code&gt; 增加 1。它们递增的顺序对于程序的其余部分是完全不可见的。&lt;&#x2F;p&gt;
&lt;p&gt;The logic for verifying that a specific reordering or other optimization won’t affect the behavior of your program does not take other threads into account. In our example above, that’s perfectly fine, as the unique references (&amp;amp;mut i32) guarantee that nothing else can possibly access the values, making other threads irrelevant. The only situation where this is a problem is when mutating data that’s shared between threads. Or, in other words, when working with atomics. This is why we have to explicitly tell the compiler and processor what they can and can’t do with our atomic operations, since their usual logic ignores interactions between threads and might allow for optimizations that do change the result of your program.&lt;br &#x2F;&gt;
验证特定重新排序或其他优化不会影响程序行为的逻辑不会考虑其他线程。在我们上面的示例中，这完全没问题，因为独占引用 (&lt;code&gt;&amp;amp;mut i32&lt;&#x2F;code&gt;) 保证没有其他任何东西可以访问这些值，从而使其他线程无关紧要。唯一会出现问题的情况是在改变线程之间共享的数据时。或者，换句话说，在使用原子时。这就是为什么我们必须明确地告诉编译器和处理器它们可以用我们的原子操作做什么和不能做什么，因为它们通常的逻辑会忽略线程之间的交互，并且可能允许进行确实会改变程序结果的优化。&lt;br &#x2F;&gt;
The interesting question is how we tell them. If we wanted to precisely spell out exactly what is and isn’t acceptable, concurrent programming might become exceedingly verbose and error prone, and maybe even architecture-specific:&lt;br &#x2F;&gt;
有趣的问题是我们如何告诉他们。如果我们想准确地说明什么是可接受的，什么是不可接受的，并发编程可能会变得非常冗长和容易出错，甚至可能是特定于体系结构的：&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D3C6AA; background-color: #2D353B;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;let x = a.fetch_add(1,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Dear compiler and processor,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Feel free to reorder this with operations on b,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    but if there&amp;#39;s another thread concurrently executing f,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    please don&amp;#39;t reorder this with operations on c!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Also, processor, don&amp;#39;t forget to flush your store buffer!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    If b is zero, though, it doesn&amp;#39;t matter.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    In that case, feel free to do whatever is fastest.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Thanks~ &amp;lt;3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Instead, we can only pick from a small set of options, represented by the &lt;code&gt;std::sync::atomic::Ordering&lt;&#x2F;code&gt; enum, which every atomic operation takes as an argument. The set of available options is very limited, but has been carefully picked to fit most use cases well. The orderings are very abstract and do not directly reflect the actual compiler and processor mechanisms involved, such as instruction reordering. This makes it possible for your concurrent code to be architecture-independent and future-proof. It allows for verification without knowing the details of every single current and future processor and compiler version.&lt;br &#x2F;&gt;
相反，我们只能从一小组选项中选择，由 &lt;code&gt;std::sync::atomic::Ordering&lt;&#x2F;code&gt; 枚举表示，每个原子操作都将其作为参数。可用选项集非常有限，但经过精心挑选以适合大多数用例。排序非常抽象，并不直接反映所涉及的实际编译器和处理器机制，例如指令重新排序。这使您的并发代码可以独立于体系结构且面向未来。它允许在不知道每个当前和未来处理器和编译器版本的详细信息的情况下进行验证。&lt;&#x2F;p&gt;
&lt;p&gt;The available orderings in Rust are:&lt;br &#x2F;&gt;
Rust 中可用的排序是：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Relaxed ordering: Ordering::Relaxed&lt;&#x2F;li&gt;
&lt;li&gt;Release and acquire ordering: Ordering::{Release, Acquire, AcqRel}&lt;&#x2F;li&gt;
&lt;li&gt;Sequentially consistent ordering: Ordering::SeqCst&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In C++, there is also something called consume ordering, which has been purposely omitted from Rust, but is nonetheless interesting to discuss as well.&lt;br &#x2F;&gt;
在 C++ 中，还有一种叫做 consume ordering 的东西，它在 Rust 中被故意省略了，但讨论起来也很有趣。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;在这一大段里我们来捞点干的，大意是编译器会优化代码，导致指令执行顺序和代码表示的顺序不一定一致，而这一点在多线程环境下被放大了。不管是否有指令重排，第一个线程实际上并不关心，因为不会影响它的执行结果，而假如发生了指令重排，使第一个线程真实的执行顺序变得和原来相反，那么第二个线程就看到了相反顺序的发生情况，如果第二个线程的操作对第一个线程的操作顺序有所依赖，这就会出现问题。所以在某些特殊场合，我们需要有能力对代码&#x2F;指令的执行顺序有所限制，如果你不想做上面 Mara 博士举例的“面向协议编程”，内存排序则是你的救星。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;The different memory ordering options have a strict formal definition to make sure we know exactly what we’re allowed to assume, and for compiler writers to know exactly what guarantees they need to provide to us. To decouple this from the details of specific processor architectures, memory ordering is defined in terms of an abstract &lt;em&gt;memory model&lt;&#x2F;em&gt;.&lt;br &#x2F;&gt;
不同的内存排序选项有一个严格的正式定义，以确保我们确切地知道我们被允许假设什么，并让编译器编写者确切地知道他们需要为我们提供什么保证。为了将其与特定处理器架构的细节分离，内存排序是根据抽象内存模型定义的。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Rust’s memory model, which is mostly copied from C++, doesn’t match any existing processor architecture, but instead is an abstract model with a strict set of rules that attempt to represent the greatest common denominator of all current and future architectures, while also giving the compiler enough freedom to make useful assumptions while analyzing and optimizing programs.&lt;br &#x2F;&gt;
Rust 的内存模型，主要是从 C++ 复制而来，不匹配任何现有的处理器架构，而是一个具有一组严格规则的抽象模型，试图代表所有当前和未来架构的最大公分母，同时也给出编译器有足够的自由在分析和优化程序时做出有用的假设。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;We’ve already seen a part of the memory model in action in &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;marabos.nl&#x2F;atomics&#x2F;basics.html#borrowing-and-races&quot;&gt;“Borrowing and Data Races” in Chapter 1&lt;&#x2F;a&gt;, where we talked about how data races result in undefined behavior. Rust’s memory model allows for concurrent atomic stores, but considers concurrent non-atomic stores to the same variable to be a data race, resulting in undefined behavior.&lt;br &#x2F;&gt;
我们已经在第 1 章的“借用和数据竞争”中看到了内存模型的一部分，我们在其中讨论了数据竞争如何导致未定义的行为。Rust 的内存模型允许并发&lt;strong&gt;原子存储&lt;&#x2F;strong&gt;，但将对同一变量的并发&lt;strong&gt;非原子存储&lt;&#x2F;strong&gt;视为数据竞争──导致未定义的行为。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;计算机的难题 99% 是靠分层解决的，Rust 的内存模型也不例外，这里借鉴了 C++，或者说是编译器前后端&#x2F;虚拟机的经验，不对某一具体处理器架构而是在这中间又加了一个抽象层，这样就能最大程度地保证灵活，除了多了一层翻译会有一点性能上的权衡以外没啥缺点。&lt;&#x2F;p&gt;
&lt;p&gt;激动，终于要谈到今天的主题──Happened-before 关系了，接着读：&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;The memory model defines the order in which operations happen in terms of &lt;em&gt;happens-before relationships&lt;&#x2F;em&gt;. This means that as an abstract model, it doesn’t talk about machine instructions, caches, buffers, timing, instruction reordering, compiler optimizations, and so on, but instead only defines situations where one thing is guaranteed to happen before another thing, and leaves the order of everything else undefined.&lt;br &#x2F;&gt;
内存模型定义了操作发生的顺序，即 happens-before 关系。这意味着，作为一个抽象的模型，它不谈论机器指令、缓存、缓冲区、时间、指令重排、编译器优化等等，只定义了一件事在另一件事之前发生的情况，而对其他事情的顺序没有定义。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;The basic happens-before rule is that everything that happens within the same thread happens in order. If a thread is executing &lt;code&gt;f(); g();&lt;&#x2F;code&gt;, then &lt;code&gt;f()&lt;&#x2F;code&gt; &lt;em&gt;happens-before&lt;&#x2F;em&gt; &lt;code&gt;g()&lt;&#x2F;code&gt;.&lt;br &#x2F;&gt;
基本的 happens-before 规则是同一线程内发生的所有事情都按顺序发生。如果线程正在执行 &lt;code&gt;f(); g();&lt;&#x2F;code&gt;，那么 &lt;code&gt;f()&lt;&#x2F;code&gt; happens-before &lt;code&gt;g()&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;这里我补充一些内容：&lt;&#x2F;p&gt;
&lt;ol start=&quot;0&quot;&gt;
&lt;li&gt;Mara 博士原文是 happens-before，这是编程语言的用法，但我更想讨论的是 happened-before 事件间的关系，故引述原文的地方我不做改动，但在我自己叙述的时候使用后者&lt;&#x2F;li&gt;
&lt;li&gt;当 A 操作 &lt;strong&gt;happened-before&lt;&#x2F;strong&gt; B 操作的时候，操作 A 先于操作 B 执行，且 A 操作的结果对 B 来说可见。每个线程都可以执行不同的任务，但是当它们之间进行一些交互时，例如读写共享内存或发送和接收消息时，必须遵守一些规则。这些规则确保了在一个线程中发生的操作在另一个线程中变得可见之前已经完成。也就是说，在一个线程中发生的操作 happened-before 了在另一个线程中看到该操作&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;happened-before&lt;&#x2F;strong&gt; 包含两种情况：&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;ul&gt;
&lt;li&gt;对于同一线程内的 happened-before，其等同于 sequenced-before（A &lt;em&gt;sequenced-before&lt;&#x2F;em&gt; B → 代表 A 的求值会先完成，之后才进行对 B 的求值）&lt;&#x2F;li&gt;
&lt;li&gt;对于不同线程内的 happened-before，其等同于 inter-thread happened before（A &lt;em&gt;inter-thread happened before&lt;&#x2F;em&gt; B → 如果操作 A happened-before 操作 B，则可以保证线程 1 中的操作 A 的执行对线程 2 中的操作 B 的执行是可见的，也就是说，操作 A 执行后对共享状态所做的修改对线程 2 可见）&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Between threads, however, happens-before relationships only occur in a few specific cases, such as when spawning and joining a thread, unlocking and locking a mutex, and through atomic operations that use non-relaxed memory ordering. &lt;code&gt;Relaxed&lt;&#x2F;code&gt; memory ordering is the most basic (and most performant) memory ordering that, by itself, never results in any cross-thread happens-before relationships.&lt;br &#x2F;&gt;
然而，在线程之间，happens-before 关系只发生在少数特定情况下，例如 spawn 线程和 join 线程、解锁和锁定互斥锁以及使用非 &lt;code&gt;Relaxed&lt;&#x2F;code&gt; 内存排序的原子操作。&lt;code&gt;Relaxed&lt;&#x2F;code&gt; 内存排序是最基本（也是最高效）的内存排序，它本身不会导致任何跨线程的 happens-before 关系。&lt;&#x2F;p&gt;
&lt;p&gt;To explore what that means, let’s take a look at the following example where we assume &lt;code&gt;a&lt;&#x2F;code&gt; and &lt;code&gt;b&lt;&#x2F;code&gt; are concurrently executed by different threads:&lt;br &#x2F;&gt;
为了探究这意味着什么，让我们看一下下面的示例，其中我们假设 &lt;code&gt;a&lt;&#x2F;code&gt; 和 &lt;code&gt;b&lt;&#x2F;code&gt; 由不同的线程同时执行：&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D3C6AA; background-color: #2D353B;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;static&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; X&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; AtomicI32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; AtomicI32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D699B6;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;static&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; Y&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; AtomicI32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; AtomicI32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D699B6;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E67E80;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt; a&lt;&#x2F;span&gt;&lt;span&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt;    X&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt;store&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D699B6;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; Relaxed&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;span style=&quot;color: #859289;font-style: italic;&quot;&gt;    &#x2F;&#x2F; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt;    Y&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt;store&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D699B6;&quot;&gt;20&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; Relaxed&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;span style=&quot;color: #859289;font-style: italic;&quot;&gt;    &#x2F;&#x2F; 2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E67E80;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt; b&lt;&#x2F;span&gt;&lt;span&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span&gt; y&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; Y&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt;load&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt;Relaxed&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;span style=&quot;color: #859289;font-style: italic;&quot;&gt; &#x2F;&#x2F; 3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span&gt; x&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt; X&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E69875;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt;load&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7FBBB3;&quot;&gt;Relaxed&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;span style=&quot;color: #859289;font-style: italic;&quot;&gt; &#x2F;&#x2F; 4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A7C080;&quot;&gt;    println!&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DBBC7F;&quot;&gt;x&lt;&#x2F;span&gt;&lt;span&gt;} {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DBBC7F;&quot;&gt;y&lt;&#x2F;span&gt;&lt;span&gt;}&amp;quot;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;As mentioned above, the basic happens-before rule is that everything that happens within the same thread happens in order. In this case: 1 happens-before 2, and 3 happens-before 4, as shown in &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;marabos.nl&#x2F;atomics&#x2F;memory-ordering.html#happens-before-diagram-load-store&quot;&gt;Figure 3-1&lt;&#x2F;a&gt;. Since we use relaxed memory ordering, there are no other happens-before relationships in our example.&lt;br &#x2F;&gt;
如上所述，基本的 happens-before 规则是同一线程内发生的所有事情都按顺序发生。在这种情况下：1 happens-before 2，3 happens-before 4，如图 3-1 所示。由于我们使用 &lt;code&gt;Relaxed&lt;&#x2F;code&gt; 内存排序，因此在我们的示例中没有其他 happens-before 关系。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;marabos.nl&#x2F;atomics&#x2F;images&#x2F;raal_0301.png&quot; loading=&quot;lazy&quot; &#x2F;&gt;
&lt;p&gt;Figure 3-1. The happens-before relationships between atomic operations in the example code.&lt;br &#x2F;&gt;
图 3-1 示例代码中原子操作之间的 happens-before 关系。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;If either of &lt;code&gt;a&lt;&#x2F;code&gt; or &lt;code&gt;b&lt;&#x2F;code&gt; completes before the other starts, the output will be &lt;code&gt;0 0&lt;&#x2F;code&gt; or &lt;code&gt;10 20&lt;&#x2F;code&gt;. If &lt;code&gt;a&lt;&#x2F;code&gt; and &lt;code&gt;b&lt;&#x2F;code&gt; run concurrently, it’s easy to see how the output can be &lt;code&gt;10 0&lt;&#x2F;code&gt;. One way this can happen is if the operations run in this order: 3 1 2 4.&lt;br &#x2F;&gt;
如果 &lt;code&gt;a&lt;&#x2F;code&gt; 或 &lt;code&gt;b&lt;&#x2F;code&gt; 中的任何一个在另一个开始之前完成，则输出将为 &lt;code&gt;0 0&lt;&#x2F;code&gt; 或 &lt;code&gt;10 20&lt;&#x2F;code&gt;。如果 &lt;code&gt;a&lt;&#x2F;code&gt; 和 &lt;code&gt;b&lt;&#x2F;code&gt; 同时运行，很容易看出输出是 &lt;code&gt;10 0&lt;&#x2F;code&gt;。发生这种情况的一种方式是操作按以下顺序运行：3 1 2 4。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;More interestingly, the output can also be &lt;code&gt;0 20&lt;&#x2F;code&gt;, even though there is no possible globally consistent order of the four operations that would result in this outcome. When 3 is executed, there is no happens-before relationship with 2, which means it could load either 0 or 20. When 4 is executed, there is no happens-before relationship with 1, which means it could load either 0 or 10. Given this, the output &lt;code&gt;0 20&lt;&#x2F;code&gt; is a valid outcome.&lt;br &#x2F;&gt;
更有趣的是，输出也可以是 &lt;code&gt;0 20&lt;&#x2F;code&gt;，&lt;strong&gt;即使没有可能导致此结果的四个操作的全局一致顺序&lt;&#x2F;strong&gt;。当 3 执行时，与 2 没有 happens-before 关系，也就是说它可以加载 0 或 20。当 4 执行时，与 1 没有 happens-before 关系，这意味着它可以加载 0 或 10。鉴于此，输出 &lt;code&gt;0 20&lt;&#x2F;code&gt; 是一个有效的结果。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;The important and counter-intuitive thing to understand is that operation 3 loading the value 20 does &lt;em&gt;not&lt;&#x2F;em&gt; result in a happens-before relationship with 2, even though that value is the one stored by 2. Our intuitive understanding of the concept of “before” breaks down when things don’t necessarily happen in a globally consistent order, such as when instruction reordering is involved.&lt;br &#x2F;&gt;
需要理解的重要且违反直觉的事情是，操作 3 加载值 20 不会导致与操作 2 为 happens-before 关系，即使该值是由操作 2 存储的。当事情不一定以全局一致顺序发生时，我们对“before”概念的直觉理解就会崩溃，例如当涉及&lt;strong&gt;指令重排&lt;&#x2F;strong&gt;时。&lt;br &#x2F;&gt;
A more practical and intuitive, but less formal, understanding is that from the perspective of the thread executing &lt;code&gt;b&lt;&#x2F;code&gt;, operations 1 and 2 might appear to happen in the opposite order.&lt;br &#x2F;&gt;
一种更实用、更直观但不太正式的理解是，从执行 &lt;code&gt;b&lt;&#x2F;code&gt; 的线程的角度来看，操作 1 和 2 的发生顺序可能相反。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;迷糊了吧，我也迷糊了，我始终无法理解 &lt;code&gt;0 20&lt;&#x2F;code&gt; 是怎么成为一个有效结果的。首先输出 &lt;code&gt;0&lt;&#x2F;code&gt;，这意味着操作 4 在操作 1 之前发生；输出 &lt;code&gt;20&lt;&#x2F;code&gt; 则意味着操作 2 在操作 3 之前发生，而最关键的“基本的 happened-before 规则是同一线程内发生的所有事情都按顺序发生”，那么也就是说操作 3 在操作 4 之前发生，操作 1 在操作 2 之前发生，我是没有想得到是什么样的全局顺序能够满足这些要求，无论怎么想都有矛盾，Mara 博士也说这个例子中并没有能导致该结果的四个操作的全局一致顺序，所以如果还在这个方向思考，肯定是难以得到简单直观解释的。&lt;&#x2F;p&gt;
&lt;p&gt;让我卖个关子（其实已有伏笔），欲知后事如何，且听下回分解。&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
