From 4416fd3948e82771148a601041fb38541ff22b85 Mon Sep 17 00:00:00 2001 From: fwqaaq Date: Tue, 5 Mar 2024 22:12:04 +0800 Subject: [PATCH] fix #95 --- 3_Memory_Ordering.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/3_Memory_Ordering.md b/3_Memory_Ordering.md index 3249296..e2d2582 100644 --- a/3_Memory_Ordering.md +++ b/3_Memory_Ordering.md @@ -459,11 +459,11 @@ consume 排序是 acquire 排序的一个轻量级、更高效的变体,其同 最强的内存排序是*顺序一致性*排序:`Ordering::SeqCst`。它包含了 acquire 排序(对于 load 操作)以及 release 排序(对于 store 操作)的所有保证,并且*也*保证了操作的全局一致排序。 -这意味着在程序中使用 `SeqCst` 排序的每个操作是所有线程都同意的单个总顺序的一部分。这个总顺序与每个变量的总修改顺序一致。 +这意味着在程序中使用 `SeqCst` 排序的每个操作是所有线程都同意的单独全序[^3](single total order,译注:可以理解为在该顺序关系中,每个操作都与其它操作有单独的顺序关系)的一部分。该全序[^4](total order,译注:该原子变量的顺序关系)与每个单独变量的总修改顺序(total modification order)一致。 由于它严格强于 acquire 和 release 内存排序,因此顺序一致性 load 或者 store 操作可以取代一对 release-acquire 中的 acquire-load 或 release-store 操作,形成 happens-before 关系。换句话说,acquire-load 不仅可以与 release-store 形成 happens-before 关系,同时也可以和顺序一致的 store 形成 happens-before 关系,同样 release-store 也是如此。 -> 仅有当 happens-before 关系的双方都使用 SeqCst 时,才能保证与 SeqCst 操作的单个总顺序一致。 +> 仅有当 happens-before 关系的双方都使用 SeqCst 时,才能保证与 SeqCst 操作的单独全序一致。 虽然这似乎是最容易推理的内存排序,但 SeqCst 排序在实践中几乎从来都没有必要。在几乎所有情况下,通常 acquire 和 release 排序就足够了。 @@ -499,7 +499,7 @@ fn main() { 两个线程首先设置它们自己的原子布尔值到 true,以告知另一个线程它们正在获取 S,并且然后检查其它的原子变量布尔值,是否它们可以安全地获取 S,而不会导致数据竞争。 -如果两个 store 操作都发生在任一 load 操作之前,则两个线程最终都无法访问 S。然而,两个线程都不可能访问 S 并导致未定义的行为,因为顺序一致的顺序保证了其中只有一个线程可以赢得竞争。在每个可能的单个总的顺序中,第一个操作将是 store 操作,这阻止其他线程访问 S。 +如果两个 store 操作都发生在任一 load 操作之前,则两个线程最终都无法访问 S。然而,两个线程都不可能访问 S 并导致未定义的行为,因为顺序一致的顺序保证了其中只有一个线程可以赢得竞争。在每个可能的单独全序中,第一个操作将是 store 操作,这阻止其他线程访问 S。 在实际情况中,几乎所有对 SeqCst 的使用都涉及一种类似的存储模式, store 操作在随后同一线程上的 load 操作之前必须成为全局可见的。对于这些情况,一个潜在的更有效的替代方案是将 relaxed 的操作与 SeqCst 屏障结合使用,我们接下来将探索。 @@ -509,7 +509,7 @@ fn main() { 除了对原子变量的额外操作,我们还可以将内存排序应用于:原子屏障。 -`std::sync::atomic::fence` 函数表示一个*原子屏障*,它可以是一个 Release 屏障、一个 Acquire 屏障,或者两者都是(AcqRel 或 SeqCst)。SeqCst 屏障还参与顺序一致性的全局排序。 +`std::sync::atomic::fence` 函数表示一个*原子屏障*,它可以是一个 Release 屏障、一个 Acquire 屏障,或者两者都是(AcqRel 或 SeqCst)。SeqCst 屏障还参与到顺序一致性的全序中。 原子屏障允许你从原子操作中分离内存排序。如果你想要应用内存排序到多个操作这可能是有用的,或者你想要有条件地应用内存排序。 @@ -634,7 +634,7 @@ fn main() { 虽然在这个特定的例子中,投入任何精力进行此类优化可能完全没有必要,但在构建高效的并发数据结构时,这种节省额外获取操作开销的模式可能很重要。 -`SeqCst` 屏障既是 release 屏障也是 acquire 屏障(就像 `AcqRel`),同时也是顺序一致性操作的单个总顺序的一部分。然而,只有屏障是总顺序的一部分,但它之前或之后的原子操作不必是总顺序的一部分。这意味着,与 release 或 acquire 操作不同,顺序一致性的操作不能拆分为 relaxed 操作和内存屏障。 +`SeqCst` 屏障既是 release 屏障也是 acquire 屏障(就像 `AcqRel`),同时也是顺序一致性操作中单独全序的一部分。然而,只有屏障是全序的一部分,但它之前或之后的原子操作不必是总顺序的一部分。这意味着,与 release 或 acquire 操作不同,顺序一致性的操作不能拆分为 relaxed 操作和内存屏障。

编译器屏障

@@ -685,7 +685,7 @@ fn main() { 抛开性能问题,顺序一致性内存排序通常被视为默认选择的理想内存排序类型,因为它具有强大的保证。确实,如果任何其他内存排序是正确的,那么 `SeqCst` 也是正确的。这可能让人觉得 `SeqCst` 总是正确的。然而,可能并发算法本身就是不正确的,不论使用哪种内存排序。 -更重要的是,在阅读代码时,`SeqCst` 基本上告诉读者:“该操作依赖于程序中每个 `SeqCst` 操作的总顺序”,这是一个极其广泛的声明。如果可能的话,使用较弱的内存排序往往会使相同的代码更容易进行审查和验证。例如,Release 明确告诉读者:“这与同一变量的 acquire 操作相关”,在形成对代码的理解时涉及的考虑要少得多。 +更重要的是,在阅读代码时,`SeqCst` 基本上告诉读者:“该操作依赖于程序中每个单独 `SeqCst` 操作的全序”,这是一个极其广泛的声明。如果可能的话,使用较弱的内存排序往往会使相同的代码更容易进行审查和验证。例如,Release 明确告诉读者:“这与同一变量的 acquire 操作相关”,在形成对代码的理解时涉及的考虑要少得多。 建议将 SeqCst 看作是一个警示标识。在实际代码中看到它通常意味着要么涉及到复杂的情况,要么简单地说是作者没有花时间分析其与内存排序相关的假设,这两种情况都需要额外的审查。 @@ -717,3 +717,7 @@ fn main() { [^1]: [^2]: +[^3]: 摘自 CPP 原子变量的内存排序翻译,请参考参见 +[^4]: 摘自 CPP 原子变量的内存排序翻译,请参考参见 + +参见: