In the following code from [Rust documentation][1] it talks about concurrent threading in Rust.
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());
}
I still couldn't grasp the idea of the for loop for the handles
i.e
for handle in handles {
handle.join().unwrap();
}
The documentation says "we call join on each handle to make sure all the threads finish. " For an experiment I commented out the handle for loop and I got an out put of 8 instead of 10. When I changed the loop to 1000, I got 999 when the handle loop is commented. What is happening here ? How does 8 & 999 become the output ?
[1]: https://doc.rust-lang.org/book/ch16-03-shared-state.html
CodePudding user response:
I still couldn't grasp the idea of the for loop for the handles
What is there to grasp? JoinHandle::join
blocks until the corresponding thread is done executing (so the function has reached its end), that's most of what there is to it (usefully it also yields whatever the thread's function has returned).
When I changed the loop to 1000, I got 999 when the handle loop is commented. What is happening here ? How does 8 & 999 become the output ?
When you don't join
on the threads, you have a race between the threads and the main thread (the main
function). The value you get is however many threads have executed the increment in the time it took to
- create all the threads
- add each threads to the vector
- get a lock on the counter
This will change depending on system load and OS scheduling details, though most of the delay of the main thread will be... spawning more threads (compared to spawning a thread, acquiring a lock and incrementing a number is cheap) which is why most of the threads are done by the time you print the results. If you increase per-thread work, or change the way the threads are spawned, you will see different races.
CodePudding user response:
What join() does is simply wait for the thread to finish. It asks the OS to block the main thread (the one calling join()
) until the joined thread is done, and collect its status.
Before calling join()
, you only know that you have handed the thread to the OS. You don't know later in the code if it has already been started, is running, has finished, was killed by the OS, or has panic()'ed, etc.
The only thing you know about your shared counter
before calling join()
is that you can read it in a consistent state (thanks to the mutex). That is, without risking a race-condition between the main thread and another thread that may be in the middle of incrementing it.