-
Every atomic object has a timeline (TL) of writes:
- A write is either a store or a read-modify-write (RMW): it read latest write & pushed new one.
- A write is either tagged Relaxed, Release, or SeqCst.
- A read observes some write on the timeline:
- On the same thread, future reads can't go backwards on the timeline.
- A read is either tagged Relaxed, Acquire, or SeqCst.
- RMWs can also be tagged Acquire (or AcqRel). If so, the Acquire refers to the "read" portion of "RMW".
-
Each thread has its own view of the world:
- Shared write timelines but each thread could be reading at different points.
- Every operation it issues happens in its "local order" as written.
- But it may see timeline writes done by other threads in a different order than their local order.
- e.g. thread-1 (T1) does
store TL-1 -> store TL-2
but T2 seesstore TL-2 -> store TL-1
.
- e.g. thread-1 (T1) does
- How to get order between multiple timelines across threads?
-
Different timelines can be ordered locally, then across thread views:
- A write tagged Release ensures any previous local memory/timelines ops happen before it.
- A read tagged Acquire ensures any future local memory/timelines ops happen after it.
- A thread which Acquire reads a Release write "syncs up" with it, even across differen thread "local orders":
- ensuring:
previous ops on T1 -> Release write on T1 -> Acquire read on T2 -> future ops on T2
- ensuring:
- In fact, that read "syncs up" with that write + a continuous chain of previous writes on that timeline including:
- other writes done by the same thread the reader "synced up" with.
- other writes that are RMWs (by any thread) regardless of its tag.
-
SeqCst creates scuffed parallel universes:
- A read tagged SeqCst also acts as Acquire.
- A write tagged SeqCst also acts as Release.
- SeqCst writes (and reads!!) are also on a global timeline called the "total order":
- SeqCst reads/writes from multiple timelines can participate in it.
- But they cant "sync up" with each other. That's only allowed for reads/writes within a single (not total order) timeline.
-
Fences sorta time travel and get pretty cursed:
- A Release fence "syncs up" with an Acquire read if the read sees a matching write made after the fence:
- ensuring:
previous ops on T1 -> Release fence on T1 -> SOME write on T1 -> Acquire read on T2 -> future ops on T2
- ensuring:
- A Acquire fence "syncs up" with a Release write if a previous read sees that write:
- ensuring:
previous ops on T1 -> Release write on T1 -> SOME read on T2 -> Acquire fence on T2 -> future ops on T2
- ensuring:
- A AcqRel fence does both.
- A SeqCst fence does both, participates in "total order", and does some extra stuff:
- Assuming some atomic variable timeline, two operations (A and B) "logically see" each other if either:
- A reads B (or something later than B the timeline).
- A is a write that occurs before B in the timeline.
- Then a SeqCst fence (X) occurs before another SeqCst fence (Y) in "total order" if:
fence X -> A
on T1,B -> fence Y
on T2, and A "logically sees" B.
- Assuming some atomic variable timeline, two operations (A and B) "logically see" each other if either:
- A Release fence "syncs up" with an Acquire read if the read sees a matching write made after the fence:
-
-
Save kprotty/bb26b963441baf2ab3486a07fbf4762e to your computer and use it in GitHub Desktop.
Offering a simpler way to conceptualize https://en.cppreference.com/w/cpp/atomic/memory_order
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nicely explained!