I am quite confused by the output of the channel chapter of Rust by Example:
use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc;
use std::thread;
static NTHREADS: i32 = 3;
fn main() {
// Channels have two endpoints: the `Sender<T>` and the `Receiver<T>`,
// where `T` is the type of the message to be transferred
// (type annotation is superfluous)
let (tx, rx): (Sender<i32>, Receiver<i32>) = mpsc::channel();
for id in 0..NTHREADS {
// The sender endpoint can be copied
let thread_tx = tx.clone();
// Each thread will send its id via the channel
thread::spawn(move || {
// The thread takes ownership over `thread_tx`
// Each thread queues a message in the channel
thread_tx.send(id).unwrap();
// Sending is a non-blocking operation, the thread will continue
// immediately after sending its message
println!("thread {} finished", id);
});
}
// Here, all the messages are collected
let mut ids = Vec::with_capacity(NTHREADS as usize);
for _ in 0..NTHREADS {
// The `recv` method picks a message from the channel
// `recv` will block the current thread if there no messages available
ids.push(rx.recv());
}
// Show the order in which the messages were sent
println!("{:?}", ids);
}
With the default NTHREADS = 3
, I got the following output:
thread 2 finished
thread 1 finished
[Ok(2), Ok(1), Ok(0)]
Why does the println!("thread {} finished", id);
in the for
loop print in reverse order? And where does the thread 0 finished
go?
When I changed to NTHREADS = 8
, something more mysterious happened:
thread 6 finished
thread 7 finished
thread 8 finished
thread 9 finished
thread 5 finished
thread 4 finished
thread 3 finished
thread 2 finished
thread 1 finished
[Ok(6), Ok(7), Ok(8), Ok(9), Ok(5), Ok(4), Ok(3), Ok(2), Ok(1), Ok(0)]
The printing order confused me even more and the thread 0 is always missing. How to explain this example?
I tried this on different computers and got the same results.
There is no guaranteed order for the threads or any coordination between them, so they will execute and send the results into the channel in an arbitrary order. That's kind of the entire point - if they are independent, you can use multiple threads.
The main thread pulls N
values from the channel, puts them into a Vec
, prints the Vec
and exits.
The main thread does not wait for the child threads to finish before exiting. The missing print is explained by the last child thread sending the value into the channel, the main thread reading it (ending the for
loop), and then the program exits. There was never a chance for the thread to print out it finished.
It is also possible that the last thread has a chance to run and print out before the main thread resumes and exits.
Each scenario might be more or less likely depending on the number of CPUs or the OS, but both are correct runs of the program.
A version of the code modified to wait for the threads shows different output:
use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc;
use std::thread;
static NTHREADS: i32 = 3;
fn main() {
// Channels have two endpoints: the `Sender<T>` and the `Receiver<T>`,
// where `T` is the type of the message to be transferred
// (type annotation is superfluous)
let (tx, rx): (Sender<i32>, Receiver<i32>) = mpsc::channel();
let handles: Vec<_> = (0..NTHREADS).map(|id| {
// The sender endpoint can be copied
let thread_tx = tx.clone();
// Each thread will send its id via the channel
thread::spawn(move || {
// The thread takes ownership over `thread_tx`
// Each thread queues a message in the channel
thread_tx.send(id).unwrap();
// Sending is a non-blocking operation, the thread will continue
// immediately after sending its message
println!("thread {} finished", id);
})
}).collect();
// Here, all the messages are collected
let mut ids = Vec::with_capacity(NTHREADS as usize);
for _ in 0..NTHREADS {
// The `recv` method picks a message from the channel
// `recv` will block the current thread if there no messages available
ids.push(rx.recv());
}
// Show the order in which the messages were sent
println!("{:?}", ids);
// Wait for threads to complete
for handle in handles {
handle.join().expect("Unable to join");
}
}
Note how, in this case, the main thread prints before the last thread exits:
thread 2 finished
thread 1 finished
[Ok(2), Ok(1), Ok(0)]
thread 0 finished
It would also be valid for these four lines to occur in just about any order: there's no reason for any child thread to print before or after the main thread prints.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With