@@ -459,11 +459,11 @@ consume 排序是 acquire 排序的一个轻量级、更高效的变体,其同
459
459
460
460
最强的内存排序是* 顺序一致性* 排序:` Ordering::SeqCst ` 。它包含了 acquire 排序(对于 load 操作)以及 release 排序(对于 store 操作)的所有保证,并且* 也* 保证了操作的全局一致排序。
461
461
462
- 这意味着在程序中使用 ` SeqCst ` 排序的每个操作是所有线程都同意的单个总顺序的一部分。这个总顺序与每个变量的总修改顺序一致 。
462
+ 这意味着在程序中使用 ` SeqCst ` 排序的每个操作是所有线程都同意的单独全序 [ ^ 3 ] (single total order,译注:可以理解为在该顺序关系中,每个操作都与其它操作有单独的顺序关系)的一部分。该全序 [ ^ 4 ] (total order,译注:该原子变量的顺序关系)与每个单独变量的总修改顺序(total modification order)一致 。
463
463
464
464
由于它严格强于 acquire 和 release 内存排序,因此顺序一致性 load 或者 store 操作可以取代一对 release-acquire 中的 acquire-load 或 release-store 操作,形成 happens-before 关系。换句话说,acquire-load 不仅可以与 release-store 形成 happens-before 关系,同时也可以和顺序一致的 store 形成 happens-before 关系,同样 release-store 也是如此。
465
465
466
- > 仅有当 happens-before 关系的双方都使用 SeqCst 时,才能保证与 SeqCst 操作的单个总顺序一致 。
466
+ > 仅有当 happens-before 关系的双方都使用 SeqCst 时,才能保证与 SeqCst 操作的单独全序一致 。
467
467
468
468
虽然这似乎是最容易推理的内存排序,但 SeqCst 排序在实践中几乎从来都没有必要。在几乎所有情况下,通常 acquire 和 release 排序就足够了。
469
469
@@ -499,7 +499,7 @@ fn main() {
499
499
500
500
两个线程首先设置它们自己的原子布尔值到 true,以告知另一个线程它们正在获取 S,并且然后检查其它的原子变量布尔值,是否它们可以安全地获取 S,而不会导致数据竞争。
501
501
502
- 如果两个 store 操作都发生在任一 load 操作之前,则两个线程最终都无法访问 S。然而,两个线程都不可能访问 S 并导致未定义的行为,因为顺序一致的顺序保证了其中只有一个线程可以赢得竞争。在每个可能的单个总的顺序中 ,第一个操作将是 store 操作,这阻止其他线程访问 S。
502
+ 如果两个 store 操作都发生在任一 load 操作之前,则两个线程最终都无法访问 S。然而,两个线程都不可能访问 S 并导致未定义的行为,因为顺序一致的顺序保证了其中只有一个线程可以赢得竞争。在每个可能的单独全序中 ,第一个操作将是 store 操作,这阻止其他线程访问 S。
503
503
504
504
在实际情况中,几乎所有对 SeqCst 的使用都涉及一种类似的存储模式, store 操作在随后同一线程上的 load 操作之前必须成为全局可见的。对于这些情况,一个潜在的更有效的替代方案是将 relaxed 的操作与 SeqCst 屏障结合使用,我们接下来将探索。
505
505
@@ -509,7 +509,7 @@ fn main() {
509
509
510
510
除了对原子变量的额外操作,我们还可以将内存排序应用于:原子屏障。
511
511
512
- ` std::sync::atomic::fence ` 函数表示一个* 原子屏障* ,它可以是一个 Release 屏障、一个 Acquire 屏障,或者两者都是(AcqRel 或 SeqCst)。SeqCst 屏障还参与顺序一致性的全局排序 。
512
+ ` std::sync::atomic::fence ` 函数表示一个* 原子屏障* ,它可以是一个 Release 屏障、一个 Acquire 屏障,或者两者都是(AcqRel 或 SeqCst)。SeqCst 屏障还参与到顺序一致性的全序中 。
513
513
514
514
原子屏障允许你从原子操作中分离内存排序。如果你想要应用内存排序到多个操作这可能是有用的,或者你想要有条件地应用内存排序。
515
515
@@ -634,7 +634,7 @@ fn main() {
634
634
635
635
虽然在这个特定的例子中,投入任何精力进行此类优化可能完全没有必要,但在构建高效的并发数据结构时,这种节省额外获取操作开销的模式可能很重要。
636
636
637
- ` SeqCst ` 屏障既是 release 屏障也是 acquire 屏障(就像 ` AcqRel ` ),同时也是顺序一致性操作的单个总顺序的一部分 。然而,只有屏障是总顺序的一部分 ,但它之前或之后的原子操作不必是总顺序的一部分。这意味着,与 release 或 acquire 操作不同,顺序一致性的操作不能拆分为 relaxed 操作和内存屏障。
637
+ ` SeqCst ` 屏障既是 release 屏障也是 acquire 屏障(就像 ` AcqRel ` ),同时也是顺序一致性操作中单独全序的一部分 。然而,只有屏障是全序的一部分 ,但它之前或之后的原子操作不必是总顺序的一部分。这意味着,与 release 或 acquire 操作不同,顺序一致性的操作不能拆分为 relaxed 操作和内存屏障。
638
638
639
639
<div class =" box " >
640
640
<h2 style =" text-align : center ;" >编译器屏障</h2 >
@@ -685,7 +685,7 @@ fn main() {
685
685
686
686
抛开性能问题,顺序一致性内存排序通常被视为默认选择的理想内存排序类型,因为它具有强大的保证。确实,如果任何其他内存排序是正确的,那么 ` SeqCst ` 也是正确的。这可能让人觉得 ` SeqCst ` 总是正确的。然而,可能并发算法本身就是不正确的,不论使用哪种内存排序。
687
687
688
- 更重要的是,在阅读代码时,` SeqCst ` 基本上告诉读者:“该操作依赖于程序中每个 ` SeqCst ` 操作的总顺序 ”,这是一个极其广泛的声明。如果可能的话,使用较弱的内存排序往往会使相同的代码更容易进行审查和验证。例如,Release 明确告诉读者:“这与同一变量的 acquire 操作相关”,在形成对代码的理解时涉及的考虑要少得多。
688
+ 更重要的是,在阅读代码时,` SeqCst ` 基本上告诉读者:“该操作依赖于程序中每个单独 ` SeqCst ` 操作的全序 ”,这是一个极其广泛的声明。如果可能的话,使用较弱的内存排序往往会使相同的代码更容易进行审查和验证。例如,Release 明确告诉读者:“这与同一变量的 acquire 操作相关”,在形成对代码的理解时涉及的考虑要少得多。
689
689
690
690
建议将 SeqCst 看作是一个警示标识。在实际代码中看到它通常意味着要么涉及到复杂的情况,要么简单地说是作者没有花时间分析其与内存排序相关的假设,这两种情况都需要额外的审查。
691
691
@@ -717,3 +717,7 @@ fn main() {
717
717
718
718
[ ^ 1 ] : < https://zh.wikipedia.org/wiki/内存排序 >
719
719
[ ^ 2 ] : < https://zh.wikipedia.org/wiki/内存屏障 >
720
+ [ ^ 3 ] : 摘自 CPP 原子变量的内存排序翻译,请参考参见
721
+ [ ^ 4 ] : 摘自 CPP 原子变量的内存排序翻译,请参考参见
722
+
723
+ 参见:< https://zh.cppreference.com/w/cpp/atomic/memory_order >
0 commit comments