Shared state overview in Rust - Time & Space Complexity
When programs share data between parts, it affects how long tasks take.
We want to see how the time to run changes as more parts share and access the data.
Analyze the time complexity of the following code snippet.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
This code creates 10 threads that share and update a single counter safely using a lock.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Loop creating 10 threads, each locking and updating shared data once.
- How many times: 10 times, once per thread.
As the number of threads increases, the program does more locking and updating steps.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 lock and update steps |
| 100 | About 100 lock and update steps |
| 1000 | About 1000 lock and update steps |
Pattern observation: The work grows directly with the number of threads sharing the state.
Time Complexity: O(n)
This means the time to complete grows in a straight line as more threads share and update the state.
[X] Wrong: "Adding more threads won't affect time much because they run at the same time."
[OK] Correct: Even if threads run together, they must wait to lock the shared data, so more threads mean more waiting and longer total time.
Understanding how shared data affects time helps you write safe and efficient programs that work well when many parts run together.
"What if we replaced the Mutex with a lock-free atomic counter? How would the time complexity change?"
Practice
Arc in Rust when working with shared state?Solution
Step 1: Understand what
ArcdoesArcstands for Atomic Reference Counting. It allows multiple parts of a program to own the same data safely, especially across threads.Step 2: Differentiate from locking mechanisms
Mutexis used to lock data for safe access, butArcis about shared ownership, not locking.Final Answer:
To allow multiple ownership of the same data safely across threads -> Option BQuick Check:
Arc= shared ownership [OK]
- Confusing Arc with Mutex
- Thinking Arc locks data
- Assuming Arc copies data
Arc and Mutex in Rust?Solution
Step 1: Understand the order of wrapping
To share mutable data safely, wrap the data in aMutexfirst, then wrap that in anArcfor shared ownership.Step 2: Check each option
let data = Arc::new(Mutex::new(5)); correctly wraps 5 in a Mutex, then in an Arc. let data = Mutex::new(Arc::new(5)); reverses the order, which is incorrect. Options C and D miss one of the wrappers.Final Answer:
let data = Arc::new(Mutex::new(5)); -> Option DQuick Check:
Arc wraps Mutex for shared mutable data [OK]
- Reversing Arc and Mutex order
- Missing Mutex for mutability
- Using Arc without Mutex for mutable data
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..3 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("{}", *counter.lock().unwrap());
}Solution
Step 1: Understand the shared counter
The counter is wrapped inArcandMutex, allowing safe shared mutable access across threads.Step 2: Analyze thread increments
Three threads each lock the mutex, increment the counter by 1, then unlock. After all threads join, the counter should be 3.Final Answer:
3 -> Option AQuick Check:
3 threads increment counter once each = 3 [OK]
- Forgetting to clone Arc for each thread
- Ignoring Mutex lock for safe access
- Assuming race conditions cause wrong output
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
let data2 = data.clone();
let handle = thread::spawn(move || {
data2.lock().unwrap().push(4);
});
handle.join().unwrap();
println!("{:?}", data.lock().unwrap());
}Solution
Step 1: Check Arc cloning
Usingdata.clone()on anArcis valid and clones the pointer safely.Step 2: Verify Mutex locking and thread usage
The mutex is locked with unwrap(), vector is safely mutated inside the thread, and join waits for thread completion.Final Answer:
No error, code runs and prints [1, 2, 3, 4] -> Option AQuick Check:
Arc clone and Mutex lock correct [OK]
- Thinking Arc::clone() is mandatory
- Forgetting to unwrap Mutex lock
- Believing vectors can't be shared across threads
Arc and Mutex to achieve this?Solution
Step 1: Understand shared mutable state needs
To share and mutate data safely across threads, useArcfor shared ownership andMutexfor safe mutable access.Step 2: Analyze each option
Wrap the counter inArc<Mutex<i32>>, clone Arc for each thread, lock mutex, check if even, then increment correctly wraps the counter inArc<Mutex<i32>>, clones Arc for threads, locks mutex before checking and incrementing. Other options misuse order or omit locking.Final Answer:
Wrap the counter inArc<Mutex<i32>>, clone Arc for each thread, lock mutex, check if even, then increment -> Option CQuick Check:
Arc + Mutex + lock for safe conditional increment [OK]
- Not locking mutex before mutate
- Using Arc without Mutex for mutability
- Cloning Mutex instead of Arc
Ready to prove it?
Pass the test and this pattern moves unknown
