liveBook · Manning

Acquire semantics

As explained in Latency

An acquire memory barrier prevents subsequent memory operations from being reordered before a synchronization operation and observes effects published by a releasing operation.

Acquire semantics prevent memory operations after an acquire operation from being reordered before it. They are normally paired with release semantics: a writer publishes its earlier updates with a release operation, and a reader that observes that publication through an acquire operation can then read those updates. An acquire barrier combines the ordering provided by LoadLoad and LoadStore barriers. section 8.4.1 section 8.4.3

A completion flag

Suppose one thread stores 1 in value, then sets done to true. Another thread reads done before reading value. With relaxed ordering on both variables, it can observe done == true and value == 0: the flag appears complete while the result is stale. Store true to done with Release and load done with Acquire. If that load observes true, the subsequent read of value sees the earlier update. Reading false makes no such claim about whether the update has happened. section 8.4.3

use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;

fn main() {
    for _ in 0..1000000 {
        let value = Arc::new(AtomicUsize::new(0));
        let done = Arc::new(AtomicBool::new(false));

        let value1 = value.clone();
        let done1 = done.clone();
        let t1 = thread::spawn(move || {
            value1.store(1, Ordering::Relaxed);
            done1.store(true, Ordering::Release);
        });
        let t2 = thread::spawn(move || {
           let done = done.load(Ordering::Acquire);
           let value = value.load(Ordering::Relaxed);
           println!("value = {}, done = {}", value, done);
        });
        t1.join().unwrap();
        t2.join().unwrap();
    }
}

The Rust listing puts Release on the writer’s done store and Acquire on the reader’s done load; value remains relaxed. section 8.4.3

In a bounded single-producer, single-consumer queue, the producer writes an array slot before publishing the updated back index with Release. The consumer loads back with Acquire before reading that slot. The front index uses the corresponding ordering in the opposite direction. Loads of an index by the thread that alone updates it can remain relaxed when that thread needs no ordering guarantee from the load. Acquire and release provide the needed ordering without full mutual exclusion; the compiler supplies the appropriate implementation for the target architecture. section 8.6 section 8.4.1

How Latency covers it

How deep it goes
Defines the ordering guarantee, demonstrates a relaxed-order failure and repair, and applies it in queue operations without detailing architecture-specific instructions.
Examples
Uses a two-thread value-and-completion-flag test and a bounded single-producer, single-consumer queue.
What it assumes
Assumes familiarity with threads and shared variables while demonstrating atomic ordering through a runnable Rust example.
What you can do after
Apply it via Rust atomic ordering to pair an acquire load with a publishing release store.
How it connects
Connects acquire semantics to release semantics, relaxed ordering, memory barriers, atomic operations, and wait-free queues.

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