From what I've learned, I should always choose Arc<T>
for shared read access across threads and Arc<Mutex<T>>
for shared write access across threads. Are there cases where I don't want to use Arc<T>
/Arc<Mutex<T>>
and instead do something completely different? E.g. do something like this:
unsafe impl Sync for MyStruct {}
unsafe impl Send for MyStruct {}
let shared_data_for_writing = Arc::from(MyStruct::new());
Besides Arc<T>
, we can share objects across threads using scoped threads, e.g. by using crossbeam::scope
and Scope::spawn
. Scoped threads allow us to send borrowed pointers (&'a T
) to threads spawned in a scope. The scope guarantees that the thread will terminate before the referent is dropped. Borrowed pointers have no runtime overhead compared to Arc<T>
(Arc<T>
takes a bit more memory and needs to maintain a reference counter using atomic instructions).
Mutex<T>
is the most basic general-purpose wrapper for ensuring at most one thread may mutate a value at any given time. Mutex<T>
has one drawback: if there are many threads that only want to read the value in the mutex, they can't do so concurrently, even though it would be safe. RwLock<T>
solves this by allowing multiple concurrent readers (while still ensuring a writer has exclusive access).
Atomic types such as AtomicUsize
also allow mutation across threads, but only for small values (8, 16, 32 or 64 bits – some processors support atomic operations on 128-bit values, but that's not exposed in the standard library yet; see atomic::Atomic
for that). For example, instead of Arc<Mutex<usize>>
, you could use Arc<AtomicUsize>
. Atomic types do not require locking, but they are manipulated through atomic machine instructions. The set of atomic instructions is a bit different from the set of non-atomic instructions, so switching from a non-atomic type to an atomic type might not always be a "drop-in replacement".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With