Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do the channels work in Rust By Example?

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.

like image 530
asdetrefle Avatar asked Aug 15 '16 15:08

asdetrefle


1 Answers

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.

like image 100
Shepmaster Avatar answered Oct 23 '22 15:10

Shepmaster