Shared-State Concurrency
Recipe | Crates | Categories |
---|---|---|
Mutexes | ||
Parking Lot | ||
Atomics | ||
arc-swap |
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
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
⮳ 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
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
The ArcSwap type is a container for an Arc that can be changed atomically. Semantically, it is similar to something like Atomic<Arc