Rust by Example: Thread Communication Channels
Communicate safely between threads using Channels. This example demonstrates the 'message passing' concurrency model, showing how to send data from multiple producers to a single consumer without sharing memory.
Code
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
// Create a channel
// tx = transmitter, rx = receiver
// mpsc = Multiple Producer, Single Consumer
let (tx, rx) = mpsc::channel();
// Spawn a thread to send messages
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_millis(200));
}
});
// Receive messages in the main thread
// treating rx as an iterator blocks until channel closes
for received in rx {
println!("Got: {}", received);
}
// --- Multiple Producers ---
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone(); // Clone the transmitter
thread::spawn(move || {
tx1.send(String::from("Message from thread 1")).unwrap();
});
thread::spawn(move || {
tx.send(String::from("Message from thread 2")).unwrap();
});
for received in rx {
println!("Got: {}", received);
}
}Explanation
One of Rust's concurrency mottos is: "Do not communicate by sharing memory; instead, share memory by communicating." This approach is implemented via Channels. A channel allows you to send data from one thread to another safely, avoiding the pitfalls of shared mutable state.
Rust's standard library provides mpsc channels, which stands for Multiple Producer, Single Consumer. This means you can have many threads sending messages (by cloning the transmitter), but only one thread receiving them. The channel is created with mpsc::channel(), which returns a tuple containing the transmitter (tx) and the receiver (rx).
Sending a value down a channel transfers ownership of that value. This is a critical safety feature: once you send a value, the sending thread can no longer use it. This prevents race conditions where two threads might try to modify the same data simultaneously. The receiver blocks waiting for messages and automatically unblocks when a message arrives.
Code Breakdown
mpsc::channel(). Creates an asynchronous, unbounded channel. It returns (Sender, Receiver). If you need a bounded channel (with backpressure), use mpsc::sync_channel(size) instead.thread::spawn(move || ...). We move the transmitter tx into the thread. This is necessary because the thread needs to own the transmitter to send messages through it.tx.send(val). Sends the value. This moves val into the channel. If we tried to use val after this line, the compiler would error. The unwrap handles the case where the receiver has already hung up (disconnected).for received in rx. The receiver implements the Iterator trait. This loop blocks waiting for new messages and terminates only when the channel is closed (i.e., when all tx clones are dropped).
