Memory Model at language level with atomics

Since 2011, by default C++ guarantees SC-DRF (default sequential consistency for data-race free programs).

However you can use atomics to

std::atomic<bool> x, y;
// ... in one thread
x.store(true, std::memory_order_seq_cst);
while (x.load(std::memory_order_seq_cst)) {};
// ... in another thread
while (!x.load(std::memory_order_seq_cst)) {};
x.store(false, std::memory_order_seq_cst);

As can be seen above atomics can take in an optional memory-ordering argument.

enum std::memory_order {
  memory_order_seq_cst, // default
  memory_order_relaxed,
  // LOAD ACQUIRE - STORE RELEASE
  memory_order_release, // store ops only
  memory_order_acquire, // load ops only
  // RMW ops can do all of the above plus
  memory_order_acq_rel,
}

Operation/Transaction ordering

Useful tool to generate memory order graph: [http://svr-pes20-cppmem.cl.cam.ac.uk/cppmem/]

Example Memory Order

Sequenced before

(Program order) If eval A is sequenced before B, then resp(A) < inv(B).

Synchronizes-with relationship

(Object order) Given atomic store on x by thread 1, and atomic load on x by thread 2 reads the value written by thread 1 or by any thread after, then the store synchronizes-with the load.

Between atomic load and store operations,

Inter-thread happens-before

  1. A synchronizes-with B (object order)
  2. A sync-with X, X seq-before B
  3. A seq-before X, X inter-thread happens-before B
  4. A inter-thread happens-before X, X inter-thread happens-before B

Inter-thread happens-before

Happens-before

  1. A is sequenced-before B
  2. A interthread happens-before B

Visible side effect

The side effect A on a scalar M is visible to B on M if

  1. A happen-before B AND
  2. No other side effect on M where A hb X and X hb B

Modification order

Object order of writes

Memory Orderings

Sequential Consistency memory_order_seq_cst

Sequential Consistency (SC) guarantees that all threads must see the same process order.

If the target architecture is a weakly ordered machine, SC incurs penalty as extra insns have to be inserted.

Sequential orddering:

Non-SC orderings only agree on the modification order of each object but not the global order.

Relaxed Ordering

Relaxed ordering only guarantees that all threads will see the same object order.

Example of relaxed ordering

For a variable v,

Acquire release ordering

If a thread A does an atomic store tagged memory_order_release and a thread B does an atomic load tagged memory_order_acquire

all ops before the thread A’s atomic store op become visible to thread B.

This includes non-atomic operations!!

Tutorial

  1. Don’t remove read writes? std::atomic (or volatile)
  2. No reordering allowed? std::memory_order_seq_cst
  3. Anyone who sees X should see my past work std::memory_order_acq_rel
  4. Just care about sharing X, but not my past work std::memory_order_relaxed
  5. I want threads to agree on the same order seq_cst. Specific single total order requires SC.
  6. Increment counter? relaxed don’t care about
  7. Read/update X, then update Y in that order and mustbe visible to all other acq_rel

SC guarantees that there is a fixed total ordering of all operations across all variables that all the threads see.

Acquire Release not longer guarantees this total ordering across different vars seen by each thread may be different.

Attempt to create DAG; if cycles are formed then deadlock exists.

Relaxed: Operations are not reordered by the compiler, but the processor relaxes constraints on that level.