Sharing data through channels

An alternative approach to sharing data between threads, is to pass messages between threads using so-called: channels. By employing channels, each thread owns its own data, and the messages are synchronized in a thread-safe manner. Let's re-write our example using channels.

use tokio::join;
use tokio::spawn;
use tokio::sync::mpsc;

#[derive(Debug)]
struct User {
    id: String,
}

async fn lookup_user(id: &str, mut user_chan: mpsc::Sender<User>) {
    user_chan
        .send(User { id: id.to_string() })
        .await
        .expect("can not send user on channel");
}

#[tokio::main]
async fn main() {
    let (tx, mut rx) = mpsc::channel(100);
    let mut users = vec![];

    spawn(async move {
        join! {
            lookup_user("bob", tx.clone()),
            lookup_user("tom", tx),
        }
    });

    while let Some(user) = rx.recv().await {
        println!("received: {user:?}");
        users.push(user);
    }

    println!("Users: {users:?}");
}

As you can see, the users list does not need to be wrapped in a Mutex. This is because the main thread is the only thread that is manipulating the users Vec. The lookup_user() function is returning the User through the Sender half of the mpsc::channel. The data on the channel is automatically synchronized between threads.

mpsc stands for 'multi-producer, single-consumer' and supports sending many values from many producers to a single consumer. See Module tokio::sync for other channel types.

The channel constructor returns a tuple: tx and rx. These represent the Sender and Receiver halves of the channel. Only the Sender half can be cloned, hence the multi-producer term.

Notice that we are using the join! macro from the tokio crate to await both lookup_user tasks.

If at all possible, prefer channels above sharing data between threads. It saves you from performance bottlenecks, where threads are waiting on a Mutex lock. Channels, like the above, are buffered such that (up to a certain point) threads can continue to send data without a delay.

Exercises

Now that you've learned about asynchronous programming with tokio, it's time to put your knowledge to the test. Check out the corresponding exercise in the Exercises section. See you next time!

Reference material