Shared-State Concurrency

RecipeCratesCategories
Mutexescat-concurrency
Parking Lotcat-concurrency
Atomicscat-concurrency
arc-swapcat-concurrency

Channels are similar to single ownership, because once you transfer a value down a channel, you should no longer use that value. Shared memory concurrency is like multiple ownership: multiple threads can access the same memory location at the same time.

The Rust standard library provides smart pointer types, such as Mutex<T> and Arc<T>, that are safe to use in concurrent contexts.

Mutex

std cat-concurrency

Allow access to data from one thread at a time.

use std::sync::Arc;
use std::sync::Mutex;
use std::thread;

fn main() {
    // We wrap Mutex in Arc to allow for multiple owners.
    // Arc<T> is safe to use in concurrent situations.
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        // `clone` is somewhat a misnomer; it creates another pointer to the
        // same Mutex, increasing the strong reference count.
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(
            move || {
                let mut num = counter.lock().unwrap();
                *num += 1;
            }, /* Releases the lock automatically when the MutexGuard
                * goes out of scope. */
        );
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

Parking Lot

parking_lot parking_lot-crates.io cat-concurrency

parking_lot⮳ provides implementations of parking_lot::Mutex⮳, parking_lot::RwLock⮳, parking_lot::Condvar⮳ and parking_lot::Once⮳ that are smaller, faster and more flexible than those in the Rust standard library. It also provides a parking_lot::ReentrantMutex⮳ type.

std::sync::Mutex works fine, but parking_lot is faster.

use parking_lot::Once;

static START: Once = Once::new();

fn main() {
    // run a one-time initialization
    START.call_once(|| {
        // run initialization here
    });
}
use parking_lot::RwLock;

fn main() {
    let lock = RwLock::new(5);

    // many reader locks can be held at once
    {
        let r1 = lock.read();
        let r2 = lock.read();
        assert_eq!(*r1, 5);
        assert_eq!(*r2, 5);
    } // read locks are dropped at this point

    // only one write lock may be held, however
    {
        let mut w = lock.write();
        *w += 1;
        assert_eq!(*w, 6);
    } // write lock is dropped here
}

Atomics

std crossbeam cat-concurrency

Atomic types in std::sync::atomic⮳ provide primitive shared-memory communication between threads, and are the building blocks of other concurrent types. It defines atomic versions of a select number of primitive types, including std::sync::atomic::AtomicBool⮳, std::sync::atomic::AtomicIsize⮳, std::sync::atomic::AtomicUsize⮳, std::sync::atomic::AtomicI8⮳, std::sync::atomic::AtomicU16⮳, etc.

use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;

static GLOBAL_THREAD_COUNT: AtomicUsize = AtomicUsize::new(0);

fn main() {
    let old_thread_count = GLOBAL_THREAD_COUNT.fetch_add(1, Ordering::SeqCst);
    println!("live threads: {}", old_thread_count + 1);
}

The most common way to share an atomic variable is to put it into an std::sync::Arc⮳ (an atomically-reference-counted shared pointer).

crossbeam⮳ also offers crossbeam::atomic::AtomicCell⮳, a thread-safe mutable memory location. This type is equivalent to std::cell::Cell⮳, except it can also be shared among multiple threads.

use crossbeam_utils::atomic::AtomicCell;

fn main() {
    let a = AtomicCell::new(7);
    let v = a.into_inner();

    assert_eq!(v, 7);
}

arc-swap

arc-swap arc-swap-crates.io arc-swap-github arc-swap-lib.rs

The ArcSwap type is a container for an Arc that can be changed atomically. Semantically, it is similar to something like Atomic<Arc> (if there was such a thing) or RwLock<Arc> (but without the need for the locking). It is optimized for read-mostly scenarios, with consistent performance characteristics.