Sharred Memory Between Threads in Rust
While Go promotes communication via channels, Rust provides shared memory concurrency as a core primitive. This approach, though potentially error-prone, offers performance benefits and is fundamental to systems programming.
Core Concepts
Rust's shared memory concurrency relies on two key components:
- Arc<T> (Atomic Reference Counting): Allows multiple threads to share ownership of data. Unlike Rc, Arc uses atomic operations for thread-safe reference counting.
- Mutex<T> (Mutual Exclusion): Ansures only one thread can access the shared data at a time, preventing data races.
Implementation Example
The following demonstrates a counter shared across mulitple threads:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let shared_counter = Arc::new(Mutex::new(0));
let mut thread_handles = Vec::new();
for _ in 0..10 {
let counter_ref = Arc::clone(&shared_counter);
let handle = thread::spawn(move || {
let mut guard = counter_ref.lock().unwrap();
*guard += 1;
println!("Thread {:?} incremented to {}",
thread::current().id(), *guard);
});
thread_handles.push(handle);
}
for handle in thread_handles {
handle.join().unwrap();
}
println!("Final count: {}", *shared_counter.lock().unwrap());
}
Deadlock Scenario
Deadlocks occur when threads wait for each other's resources. This example creates a deliberate deadlock:
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
fn main() {
let resource_a = Arc::new(Mutex::new(5));
let resource_b = Arc::new(Mutex::new(10));
let a1 = Arc::clone(&resource_a);
let b1 = Arc::clone(&resource_b);
let thread_one = thread::spawn(move || {
let _lock_a = a1.lock().unwrap();
thread::sleep(Duration::from_millis(10));
let _lock_b = b1.lock().unwrap(); // May never acquire
println!("Thread one acquired both resources");
});
let a2 = Arc::clone(&resource_a);
let b2 = Arc::clone(&resource_b);
let thread_two = thread::spawn(move || {
let _lock_b = b2.lock().unwrap();
thread::sleep(Duration::from_millis(10));
let _lock_a = a2.lock().unwrap(); // May never acquire
println!("Thread two acquired both resources");
});
thread_one.join().unwrap();
thread_two.join().unwrap();
}
Send and Sync Traits
Rust uses marker traits to enforce thread safety:
- Send: Types implementing Send can be transferred between threads. Most Rust types are Send, except Rc<T>.
- Sync: Types implementing Sync can be referenced from multiple threads safely. Types composed of Sync types are automatically Sync.
Arc's implementation ensures thread safety:
// Arc is both Send and Sync when the contained type is
unsafe impl<t: send="" sync=""> Send for Arc<t> {}
unsafe impl<t: send="" sync=""> Sync for Arc<t> {}</t></t:></t></t:>
Best Practices
- Use Arc for shared ownership across threads instead of Rc
- Protect shared data with Mutex to prevent concurrent modification
- Avoid deadlocks by:
- Acquiring locks in consistent order
- Using try_lock when appropriate
- Implementing timeout mechanisms
- Leverage atomic types from std::sync::atomic for simple operations