Skip to content

Instantly share code, notes, and snippets.

@kprotty
Last active January 14, 2025 20:48
Show Gist options
  • Save kprotty/bb26b963441baf2ab3486a07fbf4762e to your computer and use it in GitHub Desktop.
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
  1. 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".
  2. 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 sees store TL-2 -> store TL-1.
    • How to get order between multiple timelines across threads?
  3. 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
    • 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.
  4. 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.
  5. 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
    • 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
    • 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.
@sidd-kulk
Copy link

Nicely explained!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment