Thread-Safe Arc(Atomically Reference Counted) = Shared pointer
data shared b/w threads (vec![1,2,3])
refCount 1 2 n
/\ /\ /\
| | |
Arc::clone() Arc::clone() Arc::clone()
thread-1 thread-2 thread-n
-
Arc provides shared ownership of immutable data across multiple
threads, With Arc::clone() reference count is incremented, and resource
is freed when count=0
if you try to change data wrapped inside Arc, It gives compilation error
Features of Arc
-
1. Shared Ownership:
- Arc<T> provides shared ownership of a value of type T, allocated in the heap. clone() on Arc produces a new Arc instance, which points to the same allocation on the heap as the source Arc, while increasing a reference count.
- When the last Arc pointer to a given allocation is destroyed, the value stored in that allocation (inner value) is also dropped.
2. Mutable References:
- Shared references in Rust disallow mutation by default, and Arc is no exception: you cannot obtain a mutable reference to something inside an Arc
- If you need to mutate through an Arc, use Mutex, RwLock, or one of the Atomic types. Eg: Arc<Mutex<Value>> or Arc<RwLock<Value>>
3. Thread Safety:
- Unlike Rc <T>, Arc <T> uses atomic operations for its reference counting.
- The disadvantage is that atomic operations are more expensive than ordinary memory accesses. Consider using Rc<T> for lower overhead
4. Send, Sync Triats on Arc <T>:
Arc<T> will implement Send and Sync as long as the T implements Send and Sync
Changing data in Arc gives Compilation Error
use std::{sync::Arc, thread};
fn main() {
let mut data = Arc::new(vec![1,2,3]);
let mut handles = vec![];
for i in 0..3 {
let data_clone = Arc::clone(&data);
//data_clone[2] = 1; //Compilation Error
let h = thread::spawn(move||{
println!("{} {:?}",i, data_clone);
});
handles.push(h);
}
for i in handles {
i.join().unwrap();
}
}
Rc<T> vs Arc<T>
| RC<T> (Reference Counted Smart Pointer) | Arc<T> (Atomically Reference Counted) | |
|---|---|---|
| What | Reference Counted Smart Pointer | Same (Internally Uses atomic operations for reference counting) |
| Thread Safe | No | Yes |
| What | Reference Counted Smart Pointer | Same |
| Memory overhead | Less | More (Since it uses atomic operations Internally) |
| clone() |
Invoking clone produces a new pointer to the same allocation in the
heap When the last Rc pointer to a given allocation is destroyed, the value stored in that allocation (often referred to as “inner value”) is also dropped |
Same |
Arc<Mutex>
Mutex without Arc used inside threads(Code not compile)
-
Problem:
std::sync::Mutex does not implement the
Copy trait, hence it cannot be moved between multiple threads.
That means if we want to use mutex between threads, shared reference (ie Arc) need to be used
use std::sync::Mutex;
use std::thread;
fn main() {
let mtx = Mutex::new(0); //Initialize a Mutex protecting an integer
let mut handles = vec![];
for i in 0..2 {
let h = thread::spawn(move || { //Spawn threads
let mut n = mtx.lock().unwrap();
*n += 1; // Modify the protected data
});
handles.push(h);
}
for h in handles { // Wait for all threads to finish
h.join().unwrap();
}
}
$ cargo run
`mutex` has type `Mutex <i32>`, which does not implement the `Copy` trait
10 | let h = thread::spawn(move || {..})
| ------- value moved into closure here, in previous iteration of loop
Arc <Mutex> Code
-
How Using Mutex inside Arc solves the problem?
Arc is Shared reference counting.
That means Mutex is not copied between threads, but only reference is incremented.
use std::sync::{Mutex, Arc};
use std::thread;
fn main() {
let mtx = Arc::new(Mutex::new(0)); // Initialize a Mutex protecting an integer
let mut handles = vec![];
for i in 0..2 {
let c = Arc::clone(&mtx); // Every thread will have new Reference. Arc is reference counted
let h = thread::spawn(move || { // Spawn threads
let mut n = c.lock().unwrap();
*n += 1; // Modify the protected data
});
handles.push(h);
}
for h in handles {
h.join().unwrap(); // Wait for all threads to finish
}
// Final value after all increments
let c1 = Arc::clone(&mtx);
let n = c1.lock().unwrap();
println!("Final value: {}", *n); // 2
}
$ cargo run
Thread 0 incremented value to 1
Thread 1 incremented value to 2
Final value: 2