Arc and Thread Safety
Arc and Thread Safety
While Rc<T> provides shared ownership for single-threaded code, it cannot be safely shared across threads because its reference count operations are not atomic. For multi-threaded scenarios, Rust provides Arc<T> (Atomically Reference Counted), which uses atomic operations to safely manage reference counts across thread boundaries.
Arc<T> works similarly to Rc<T> but is thread-safe. The "atomic" part means that incrementing and decrementing the reference count is done using atomic CPU operations, which are guaranteed to complete without interference from other threads. This makes Arc<T> slightly slower than Rc<T> due to the synchronization overhead, so you should only use Arc<T> when you actually need to share data across threads.
Common use cases for Arc<T> include:
- Sharing read-only configuration across worker threads
- Sharing immutable data structures in parallel computations
- Combined with
Mutex<T>orRwLock<T>for shared mutable state - Thread pools that share access to resources
Key Differences from Rc
| Feature | Rc<T> | Arc<T> |
|---|---|---|
| Thread-safe | No | Yes |
| Performance | Faster | Slightly slower |
| Marker traits | !Send, !Sync | Send + Sync (if T is) |
| Use case | Single-threaded | Multi-threaded |
Key Operations
Arc::new(value)- Create a new atomic reference-counted pointerArc::clone(&arc)- Create a new reference (increments count atomically)Arc::strong_count(&arc)- Get the current number of strong referencesArc::weak_count(&arc)- Get the current number of weak referencesArc::downgrade(&arc)- Create aWeak<T>referenceweak.upgrade()- Try to convert aWeak<T>back toArc<T>
Your Task
Implement the following types and functions to demonstrate Arc<T> patterns:
Part 1: Basic Arc Operations
create_arc<T>(value: T) -> Arc<T>- Create a new atomic reference-counted valueclone_arc<T>(arc: &Arc<T>) -> Arc<T>- Clone a reference (increment count)get_strong_count<T>(arc: &Arc<T>) -> usize- Get the strong reference countget_value<T: Clone>(arc: &Arc<T>) -> T- Get a clone of the inner value
Part 2: Thread-Safe Shared Data
SharedConfig- A thread-safe configuration that can be shared across threads:new( app_name: String, max_connections: usize, debug_mode: bool ) -> Arc<Self>app_name(&self) -> &strmax_connections(&self) -> usizedebug_mode(&self) -> bool
Part 3: Weak References
create_weak<T>(arc: &Arc<T>) -> Weak<T>- Create a weak referenceupgrade_weak<T>(weak: &Weak<T>) -> Option<Arc<T>>- Try to upgrade a weak referenceget_weak_count<T>(arc: &Arc<T>) -> usize- Get the weak reference count
Part 4: Thread-Safe Counter
AtomicCounter- A thread-safe counter usingArc<AtomicUsize>:new() -> Self- Create with initial value 0new_with_value(value: usize) -> Self- Create with specific valueget(&self) -> usize- Get current valueincrement(&self) -> usize- Increment and return previous valuedecrement(&self) -> usize- Decrement and return previous valueadd(&self, val: usize) -> usize- Add and return previous valueclone_counter(&self) -> Self- Create another handle to the same counter
Part 5: Shared Data with Mutex
SharedVec<T>- A thread-safe vector usingArc<Mutex<Vec<T>>>:new() -> Self- Create empty shared vecpush(&self, value: T)- Add an elementpop(&self) -> Option<T>- Remove and return last elementlen(&self) -> usize- Get current lengthis_empty(&self) -> bool- Check if emptyget(&self, index: usize) -> Option<T>where T: Clone - Get element at indexclone_vec(&self) -> Self- Create another handle to the same vec
Examples
use arc_thread_safety::*;
use std::sync::Arc;
use std::thread;
// Basic Arc operations
let shared = create_arc(42);
assert_eq!(get_strong_count(&shared), 1);
let cloned = clone_arc(&shared);
assert_eq!(get_strong_count(&shared), 2);
// Sharing across threads
let data = create_arc(vec![1, 2, 3]);
let data_clone = clone_arc(&data);
let handle = thread::spawn(move || {
// data_clone is accessible in this thread
assert_eq!(data_clone.len(), 3);
});
handle.join().unwrap();
// Shared configuration
let config = SharedConfig::new(
"MyApp".to_string(),
100,
true
);
let config_clone = Arc::clone(&config);
thread::spawn(move || {
assert_eq!(config_clone.app_name(), "MyApp");
}).join().unwrap();
// Atomic counter shared across threads
let counter = AtomicCounter::new();
let counter_clone = counter.clone_counter();
let handle = thread::spawn(move || {
for _ in 0..1000 {
counter_clone.increment();
}
});
for _ in 0..1000 {
counter.increment();
}
handle.join().unwrap();
assert_eq!(counter.get(), 2000);
// Thread-safe vector
let vec = SharedVec::new();
vec.push(1);
vec.push(2);
assert_eq!(vec.len(), 2);Hints
Click here for hints
- Use
Arc::new()to wrap values for thread-safe shared ownership Arc::clone(&arc)is cheap - it only atomically increments a counter- For mutable shared state, combine
ArcwithMutex:Arc<Mutex<T>> AtomicUsizeprovides lock-free atomic operations likefetch_addandfetch_sub- Use
Ordering::SeqCstfor atomic operations if unsure about memory ordering - When working with
Mutex, use.lock().unwrap()to acquire the lock (panics if poisoned) Weak<T>works the same way withArcas it does withRc- Remember:
Arc<T>isSend + Synconly ifTisSend + Sync
Run to execute your code and see test results here.