liveBook · Manning

Acquire semantics

As explained in Modern C, Third Edition

Acquire semantics are memory-ordering properties that make effects preceding a matching release operation visible after the acquire operation.

Reading a released atomic value

Start with `_Atomic(unsigned) x = 11`. One thread executes `x = 31`; another executes `--x`. The decrement reads and modifies `x` in one atomic step. If it reads the value written by `x = 31`, its read acquires from that write. Effects the writing thread produced before the assignment are then visible to the decrementing thread afterward. section 21.1

Synchronization has a direction: a writer performs a release operation, and a reader performs an acquire operation. An acquire operation synchronizes with a release in another thread when it reads the value that release wrote. This establishes a happened-before relation across the threads. An atomic read-modify-write operation, such as `--x`, can have both a reading and a writing role. section 21.1

TAKEAWAY: When a release synchronizes with an acquire, every effect that happened before the release must be visible at evaluations that happen after the acquire. The visibility guarantee depends on the synchronization; merely having a writer and a reader is not enough. section 21.1

C library calls can provide acquiring sides as well as atomic operations:

  • `thrd_join` synchronizes with completion of the corresponding thread, making that thread’s effects visible after the join. section 21.2
  • After the first `call_once` call completes its initialization, subsequent calls using the same object acquire its effects. Threads returning from those calls can use the initialized resource. section 21.2
  • A mutex release and a subsequent mutex acquisition form another synchronization pair. section 21.2
Table 21.1 C library functions that form synchronization pairs
Release Acquire
thrd_create(.., f, x) Entry to f(x)
thrd_exit by thread id or return from f Start of tss_t destructors for id
End of tss_t destructors for id thrd_join(id) or atexit/at_quick_exit handlers
call_once(&obj, g), first call call_once(&obj, h), all subsequent calls
Mutex release Mutex acquisition

The release/acquire table identifies the corresponding sides of thread, initialization, and mutex operations. section 21.2

How Modern C, Third Edition covers it

How deep it goes
Defines synchronization through the value read and traces its visibility consequence, while deferring relaxed atomic operations.
Examples
Uses an atomic assignment and decrement, then identifies acquiring sides of `thrd_join`, `call_once`, and mutex acquisition.
What it assumes
Assumes familiarity with C threads and atomic operations, while defining acquire, release, and their synchronization condition.
What you can do after
Evaluate/understand it: identify when a matching acquire makes earlier effects of a releasing thread visible.
How it connects
Connects release semantics and happened-before to atomic read-modify-write operations, thread joins, one-time initialization, and mutexes.

Also explained in

Compare how 5 books explain acquire semantics →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Modern C, Third Edition. The section links point to the parts of the book it draws on.