liveBook · Manning

Acquire semantics

As explained in Modern C

An acquire operation is a concurrency operation that establishes visibility of effects published by a corresponding release.

A read that makes earlier writes visible

Suppose one thread changes shared data and then writes an atomic object. Another thread reads the value written by that atomic operation. If the write has release semantics and the read has acquire semantics, the read synchronizes with the write. The first thread’s earlier effects are then visible to evaluations after the read in the second thread. Merely calling an operation “acquire” is not enough: the read must obtain the value written by the corresponding release. section 19.1

TAKEAWAY: Acquire semantics belong to the reader side of a synchronization; release semantics belong to the writer side. Their pairing establishes a happened-before relation across threads. An atomic read-modify-write operation can have both roles. section 19.1

C provides acquire behavior through both atomic operations and library calls:

  • An explicit atomic load can request memory_order_acquire. The corresponding release-only choice is memory_order_release, typically used with an atomic store. A read-modify-write operation can request memory_order_acq_rel. section 19.4
  • The ordinary atomic operations described here give reads acquire semantics and modifications release semantics. Explicit memory orders also permit weaker choices: memory_order_relaxed adds no synchronization requirement. section 19.1 section 19.4
  • Library synchronization pairs include thread creation and entry, thread exit and join, the first and subsequent calls to call_once for one object, and mutex release and acquisition. section 19.2

One-time initialization

A first call to call_once(&obj, g) runs g and acts as the releasing side for subsequent calls using obj. After another thread returns from its call with obj, it can use the initialized state: the writes performed during g are visible to it. section 19.2

An explicit memory order must fit the operation. A store-only atomic operation cannot request acquire semantics; an atomic load cannot request release semantics. On compare-exchange failure, the operation only loads, so its explicit form has a separate failure-order argument. section 19.4

Table 19.1. C library functions that form synchronization pairs (view table figure)

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

Compare the releasing and acquiring sides of C library calls, including thread, initialization, and mutex operations. section 19.2

How Modern C covers it

How deep it goes
Defines synchronization through a read of a released value, then identifies atomic memory orders and library-call pairs.
Examples
Uses communicating threads and call_once initialization to show when earlier modifications become visible.
What it assumes
Assumes familiarity with C threads and atomic objects while defining acquire, release, and synchronization in context.
What you can do after
Apply it via C atomic memory orders or synchronizing library calls to determine when another thread’s earlier effects are visible.
How it connects
Links acquire semantics to release semantics, happened-before relations, atomic operations, mutexes, and one-time initialization.

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. The section links point to the parts of the book it draws on.