Expand description
Useful synchronization primitives.
§The need for synchronization
Conceptually, a Rust program is a series of operations which willbe executed on a computer. The timeline of events happening in theprogram is consistent with the order of the operations in the code.
Consider the following code, operating on some global static variables:
// FIXME(static_mut_refs): Do not allow `static_mut_refs` lint#![allow(static_mut_refs)]staticmutA: u32 =0;staticmutB: u32 =0;staticmutC: u32 =0;fnmain() {unsafe{ A =3; B =4; A = A + B; C = B;println!("{A} {B} {C}"); C = A; }}It appears as if some variables stored in memory are changed, an additionis performed, result is stored inA and the variableC ismodified twice.
When only a single thread is involved, the results are as expected:the line7 4 4 gets printed.
As for what happens behind the scenes, when optimizations are enabled thefinal generated machine code might look very different from the code:
The first store to
Cmight be moved before the store toAorB,as if we had writtenC = 4; A = 3; B = 4.Assignment of
A + BtoAmight be removed, since the sum can be storedin a temporary location until it gets printed, with the global variablenever getting updated.The final result could be determined just by looking at the codeat compile time, soconstant folding might turn the wholeblock into a simple
println!("7 4 4").
The compiler is allowed to perform any combination of theseoptimizations, as long as the final optimized code, when executed,produces the same results as the one without optimizations.
Due to theconcurrency involved in modern computers, assumptionsabout the program’s execution order are often wrong. Access toglobal variables can lead to nondeterministic results,even ifcompiler optimizations are disabled, and it isstill possibleto introduce synchronization bugs.
Note that thanks to Rust’s safety guarantees, accessing global (static)variables requiresunsafe code, assuming we don’t use any of thesynchronization primitives in this module.
§Out-of-order execution
Instructions can execute in a different order from the one we define, due tovarious reasons:
Thecompiler reordering instructions: If the compiler can issue aninstruction at an earlier point, it will try to do so. For example, itmight hoist memory loads at the top of a code block, so that the CPU canstartprefetching the values from memory.
In single-threaded scenarios, this can cause issues when writingsignal handlers or certain kinds of low-level code.Usecompiler fences to prevent this reordering.
Asingle processor executing instructionsout-of-order:Modern CPUs are capable ofsuperscalar execution,i.e., multiple instructions might be executing at the same time,even though the machine code describes a sequential process.
This kind of reordering is handled transparently by the CPU.
Amultiprocessor system executing multiple hardware threadsat the same time: In multi-threaded scenarios, you can use twokinds of primitives to deal with synchronization:
- memory fences to ensure memory accesses are made visible toother CPUs in the right order.
- atomic operations to ensure simultaneous access to the samememory location doesn’t lead to undefined behavior.
§Higher-level synchronization objects
Most of the low-level synchronization primitives are quite error-prone andinconvenient to use, which is why the standard library also exposes somehigher-level synchronization objects.
These abstractions can be built out of lower-level primitives.For efficiency, the sync objects in the standard library are usuallyimplemented with help from the operating system’s kernel, which isable to reschedule the threads while they are blocked on acquiringa lock.
The following is an overview of the available synchronizationobjects:
Arc: Atomically Reference-Counted pointer, which can be usedin multithreaded environments to prolong the lifetime of somedata until all the threads have finished using it.Barrier: Ensures multiple threads will wait for each otherto reach a point in the program, before continuing execution alltogether.Condvar: Condition Variable, providing the ability to blocka thread while waiting for an event to occur.mpsc: Multi-producer, single-consumer queues, used formessage-based communication. Can provide a lightweightinter-thread synchronisation mechanism, at the cost of someextra memory.mpmc: Multi-producer, multi-consumer queues, used formessage-based communication. Can provide a lightweightinter-thread synchronisation mechanism, at the cost of someextra memory.Mutex: Mutual Exclusion mechanism, which ensures that atmost one thread at a time is able to access some data.Once: Used for a thread-safe, one-time global initialization routine.Mostly useful for implementing other types likeOnceLock.OnceLock: Used for thread-safe, one-time initialization of avariable, with potentially different initializers based on the caller.LazyLock: Used for thread-safe, one-time initialization of avariable, using one nullary initializer function provided at creation.RwLock: Provides a mutual exclusion mechanism which allowsmultiple readers at the same time, while allowing only onewriter at a time. In some cases, this can be more efficient thana mutex.
Modules§
- atomic
- Atomic types
- mpsc
- Multi-producer, single-consumer FIFO queue communication primitives.
- mpmc
Experimental - Multi-producer, multi-consumer FIFO queue communication primitives.
- nonpoison
Experimental - Non-poisoning synchronous locks.
- poison
Experimental - Synchronization objects that employ poisoning.
Structs§
- Arc
- A thread-safe reference-counting pointer. ‘Arc’ stands for ‘AtomicallyReference Counted’.
- Barrier
- A barrier enables multiple threads to synchronize the beginningof some computation.
- Barrier
Wait Result - A
BarrierWaitResultis returned byBarrier::wait()when all threadsin theBarrierhave rendezvoused. - Condvar
- A Condition Variable
- Lazy
Lock - A value which is initialized on the first access.
- Mutex
- A mutual exclusion primitive useful for protecting shared data
- Mutex
Guard - An RAII implementation of a “scoped lock” of a mutex. When this structure isdropped (falls out of scope), the lock will be unlocked.
- Once
- A low-level synchronization primitive for one-time global execution.
- Once
Lock - A synchronization primitive which can nominally be written to only once.
- Once
State - State yielded to
Once::call_once_force()’s closure parameter. The statecan be used to query the poison status of theOnce. - Poison
Error - A type of error which can be returned whenever a lock is acquired.
- RwLock
- A reader-writer lock
- RwLock
Read Guard - RAII structure used to release the shared read access of a lock whendropped.
- RwLock
Write Guard - RAII structure used to release the exclusive write access of a lock whendropped.
- Wait
Timeout Result - A type indicating whether a timed wait on a condition variable returneddue to a time out or not.
- Weak
Weakis a version ofArcthat holds a non-owning reference to themanaged allocation.- Exclusive
Experimental Exclusiveprovidesmutable access, also referred to asexclusiveaccess to the underlying value. However, it only permitsimmutable, orsharedaccess to the underlying value when that value isSync.- Mapped
Mutex Guard Experimental - An RAII mutex guard returned by
MutexGuard::map, which can point to asubfield of the protected data. When this structure is dropped (falls outof scope), the lock will be unlocked. - Mapped
RwLock Read Guard Experimental - RAII structure used to release the shared read access of a lock whendropped, which can point to a subfield of the protected data.
- Mapped
RwLock Write Guard Experimental - RAII structure used to release the exclusive write access of a lock whendropped, which can point to a subfield of the protected data.
- Reentrant
Lock Experimental - A re-entrant mutual exclusion lock
- Reentrant
Lock Guard Experimental - An RAII implementation of a “scoped lock” of a re-entrant lock. When thisstructure is dropped (falls out of scope), the lock will be unlocked.
- Unique
Arc Experimental - A uniquely owned
Arc.
Enums§
- TryLock
Error - An enumeration of possible errors associated with a
TryLockResultwhichcan occur while trying to acquire a lock, from thetry_lockmethod on aMutexor thetry_readandtry_writemethods on anRwLock.
Constants§
Type Aliases§
- Lock
Result - A type alias for the result of a lock method which can be poisoned.
- TryLock
Result - A type alias for the result of a nonblocking locking method.