From 59fde6f6bb04370499ef49ea89a4ac81a8654cb8 Mon Sep 17 00:00:00 2001 From: SabrinaJewson Date: Sun, 28 Aug 2022 20:19:40 +0100 Subject: [PATCH] =?UTF-8?q?Use=20=E2=80=9Ccoherence=E2=80=9D=20terminology?= =?UTF-8?q?=20from=20the=20start?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/atomics/acquire-release.md | 10 +++++----- src/atomics/multithread.md | 6 ++++++ src/atomics/relaxed.md | 12 ++++++------ src/atomics/seqcst.md | 5 +++-- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/atomics/acquire-release.md b/src/atomics/acquire-release.md index fa048ee..1ee7a4a 100644 --- a/src/atomics/acquire-release.md +++ b/src/atomics/acquire-release.md @@ -158,7 +158,7 @@ case that doesn’t seem to be enough, since even if atomics were used it still would have the _option_ of reading `0` instead of `1`, and really if we want our mutex to be sane, it should only be able to read `1`. -So it seems that want we _want_ is to be able to apply our arrow rules from +So it seems that want we _want_ is to be able to apply the coherence rules from before to completely rule out zero from the set of the possible values — if we were able to draw a large arrow from the Thread 1’s `+= 1;` to Thread 2’s `guard`, then we could trivially then use the rule to rule out `0` as a value @@ -198,7 +198,7 @@ These arrows are a new kind of arrow we haven’t seen yet; they are known as _happens-before_ (or happens-after) relations and are represented as thin arrows (→) on these diagrams. They are weaker than the _sequenced before_ double-arrows (⇒) that occur inside a single thread, but can still be used with -the arrow rules to determine which values of a memory location are valid to +the coherence rules to determine which values of a memory location are valid to read. When a happens-before arrow stores a data value to an atomic (via a release @@ -299,9 +299,9 @@ Thread 1 locked data Thread 2 └───────┘ ``` -We can now use the second arrow rule from before to follow _forward_ the arrow -from the `guard` bubble all the way to the `+= 1;`, determining that it is only -possible for that read to see `0` as its value. +We can now use the second coherence rule from before to follow _forward_ the +arrow from the `guard` bubble all the way to the `+= 1;`, determining that it is +only possible for that read to see `0` as its value. This leads us to the proper memory orderings for any mutex (and other locks like RW locks too, even): use `Acquire` to lock it, and `Release` to unlock it. So diff --git a/src/atomics/multithread.md b/src/atomics/multithread.md index e26fb60..fa061ed 100644 --- a/src/atomics/multithread.md +++ b/src/atomics/multithread.md @@ -213,6 +213,12 @@ guaranteed by the Abstract Machine: ╰───────╯ └────┘ ``` +These two rules combined make up the more generalized rule known as _coherence_, +which is put in place to guarantee that a thread will never see a value earlier +than the last one it read or later than a one it will in future write. Coherence +is basically required for any program to act in a sane way, so luckily the C++20 +standard guarantees it as one of its most fundamental principles. + You might be thinking that all this has been is the longest, most convoluted explanation ever of the most basic intuitive semantics of programming — and you’d be absolutely right. But it’s essential to grasp these fundamentals, diff --git a/src/atomics/relaxed.md b/src/atomics/relaxed.md index 4c4c4c6..557cab4 100644 --- a/src/atomics/relaxed.md +++ b/src/atomics/relaxed.md @@ -28,11 +28,11 @@ Thread 1 data Thread 2 └────┘ ``` -Unfortunately, the rules from before don’t help us in finding out where Thread -2’s line joins up to, since there are no arrows connecting that operation to -anything and therefore we can’t immediately rule any values out. As a result, we -end up facing a situation we haven’t faced before: there is _more than one_ -potential value for Thread 2 to read. +Unfortunately, coherence doesn’t help us in finding out where Thread 2’s line +joins up to, since there are no arrows connecting that operation to anything and +therefore we can’t immediately rule any values out. As a result, we end up +facing a situation we haven’t faced before: there is _more than one_ potential +value for Thread 2 to read. And this is where we encounter the big limitation with unsynchronized data accesses: the price we pay for their speed and optimization capability is that @@ -225,7 +225,7 @@ value we loaded. This isn’t really a problem — we can just try again and aga until we succeed, and `compare_exchange` is even nice enough to give us the updated value so we don’t have to load again. Also note that after we’ve updated our value of the atomic, we’re guaranteed to never see the old value again, by -the arrow rules from the previous chapter. +the coherence rules from the previous chapter. So here’s how it looks with these changes appplied: diff --git a/src/atomics/seqcst.md b/src/atomics/seqcst.md index 7c3dd6a..9024dac 100644 --- a/src/atomics/seqcst.md +++ b/src/atomics/seqcst.md @@ -66,8 +66,9 @@ from `Y` and `X` respectively, is it possible for them _both_ to load `false`? And looking at this diagram, there’s absolutely no reason why not. There isn’t even a single arrow connecting the left and right hand sides so far, so the load -has no restrictions on which value it is allowed to pick — and this goes for -both sides equally, so we could end up with an execution like this: +has no coherence-based restrictions on which value it is allowed to pick — and +this goes for both sides equally, so we could end up with an execution like +this: ```text a static X c d static Y b