kvarn::prelude::threading

Module atomic

1.0.0 · source
Expand description

Atomic types

Atomic types provide primitive shared-memory communication between threads, and are the building blocks of other concurrent types.

This module defines atomic versions of a select number of primitive types, including AtomicBool, AtomicIsize, AtomicUsize, AtomicI8, AtomicU16, etc. Atomic types present operations that, when used correctly, synchronize updates between threads.

Atomic variables are safe to share between threads (they implement Sync) but they do not themselves provide the mechanism for sharing and follow the threading model of Rust. The most common way to share an atomic variable is to put it into an Arc (an atomically-reference-counted shared pointer).

Atomic types may be stored in static variables, initialized using the constant initializers like AtomicBool::new. Atomic statics are often used for lazy global initialization.

§Memory model for atomic accesses

Rust atomics currently follow the same rules as C++20 atomics, specifically the rules from the intro.races section, without the “consume” memory ordering. Since C++ uses an object-based memory model whereas Rust is access-based, a bit of translation work has to be done to apply the C++ rules to Rust: whenever C++ talks about “the value of an object”, we understand that to mean the resulting bytes obtained when doing a read. When the C++ standard talks about “the value of an atomic object”, this refers to the result of doing an atomic load (via the operations provided in this module). A “modification of an atomic object” refers to an atomic store.

The end result is almost equivalent to saying that creating a shared reference to one of the Rust atomic types corresponds to creating an atomic_ref in C++, with the atomic_ref being destroyed when the lifetime of the shared reference ends. The main difference is that Rust permits concurrent atomic and non-atomic reads to the same memory as those cause no issue in the C++ memory model, they are just forbidden in C++ because memory is partitioned into “atomic objects” and “non-atomic objects” (with atomic_ref temporarily converting a non-atomic object into an atomic object).

The most important aspect of this model is that data races are undefined behavior. A data race is defined as conflicting non-synchronized accesses where at least one of the accesses is non-atomic. Here, accesses are conflict