Module async_std::sync[][src]

Synchronization primitives.

This module is an async version of std::sync.

The need for synchronization

async-std’s sync primitives are scheduler-aware, making it possible to .await their operations - for example the locking of a Mutex.

Conceptually, a Rust program is a series of operations which will be executed on a computer. The timeline of events happening in the program is consistent with the order of the operations in the code.

Consider the following code, operating on some global static variables:

static mut A: u32 = 0;
static mut B: u32 = 0;
static mut C: u32 = 0;

fn main() {
    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 addition is performed, result is stored in A and the variable C is modified twice.

When only a single thread is involved, the results are as expected: the line 7 4 4 gets printed.

As for what happens behind the scenes, when optimizations are enabled the final generated machine code might look very different from the code:

The compiler is allowed to perform any combination of these optimizations, as long as the final optimized code, when executed, produces the same results as the one without optimizations.

Due to the concurrency involved in modern computers, assumptions about the program’s execution order are often wrong. Access to global variables can lead to nondeterministic results, even if compiler optimizations are disabled, and it is still possible to introduce synchronization bugs.

Note that thanks to Rust’s safety guarantees, accessing global (static) variables requires unsafe code, assuming we don’t use any of the synchronization primitives in this module.

Out-of-order execution

Instructions can execute in a different order from the one we define, due to various reasons:

Higher-level synchronization objects

Most of the low-level synchronization primitives are quite error-prone and inconvenient to use, which is why async-std also exposes some higher-level synchronization objects.

These abstractions can be built out of lower-level primitives. For efficiency, the sync objects in async-std are usually implemented with help from the scheduler, which is able to reschedule the tasks while they are blocked on acquiring a lock.

The following is an overview of the available synchronization objects:

Examples

Spawn a task that updates an integer protected by a mutex:

use async_std::sync::{Arc, Mutex};
use async_std::task;

let m1 = Arc::new(Mutex::new(0));
let m2 = m1.clone();

task::spawn(async move {
    *m2.lock().await = 1;
})
.await;

assert_eq!(*m1.lock().await, 1);

Structs

Arc

A thread-safe reference-counting pointer. ‘Arc’ stands for ‘Atomically Reference Counted’.

Barrier

A counter to synchronize multiple tasks at the same time.

BarrierWaitResult

Returned by Barrier::wait() when all tasks have called it.

Condvar

A Condition Variable

Mutex

An async mutex.

MutexGuard

A guard that releases the mutex when dropped.

MutexGuardArc

An owned guard that releases the mutex when dropped.

RwLock

An async reader-writer lock.

RwLockReadGuard

A guard that releases the read lock when dropped.

RwLockUpgradableReadGuard

A guard that releases the upgradable read lock when dropped.

RwLockWriteGuard

A guard that releases the write lock when dropped.

Weak

Weak is a version of Arc that holds a non-owning reference to the managed allocation. The allocation is accessed by calling upgrade on the Weak pointer, which returns an Option<Arc<T>>.