Channels: mpsc
cargo run --bin 45_channelsstd::sync::mpsc is a multiple-producer, single-consumer channel. channel() returns a (Sender, Receiver) pair with an unbounded buffer. send moves the value to the other side, transferring ownership between threads. recv blocks until a value arrives, and returns Err once every Sender has been dropped and the buffer is empty.
Clone the Sender to get several producers. Iterating over the Receiver (for msg in rx) runs until all senders are gone, so drop the original Sender when you are done.
sync_channel(n) has a bounded buffer: send blocks when it is full, which gives a fast producer backpressure. sync_channel(0) is a rendezvous where every send waits for a recv. try_recv and recv_timeout never block forever.
One producer, one consumer
fn demo_basic() {
println!("-- basic --");
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send("ping").unwrap();
});
println!("{}", rx.recv().unwrap()); // blocks until ping arrives
println!("after sender dropped: {:?}", rx.recv());
let (tx, rx) = mpsc::channel();
let s = String::from("moved through the channel");
tx.send(s).unwrap();
// println!("{s}"); // error[E0382]: borrow of moved value: `s`
println!("{}", rx.recv().unwrap());
}A stream of values, consumed with a for loop
fn demo_stream() {
println!("-- stream --");
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
for i in 1..=3 {
tx.send(i * 10).unwrap();
thread::sleep(Duration::from_millis(1));
}
}); // tx dropped here, which ends the loop below
for n in rx {
print!("{n} ");
}
println!();
}Multiple producers
fn demo_multi_producer() {
println!("-- multiple producers --");
let (tx, rx) = mpsc::channel();
for id in 0..3 {
let tx = tx.clone();
thread::spawn(move || {
for j in 0..2 {
tx.send(format!("worker {id} msg {j}")).unwrap();
}
});
}
drop(tx); // without this, the loop below would wait forever
let mut got: Vec<String> = rx.iter().collect();
got.sort();
println!("{} messages: {got:?}", got.len());
}Bounded channels and backpressure
fn demo_sync_channel() {
println!("-- sync_channel --");
let (tx, rx) = mpsc::sync_channel(1);
let producer = thread::spawn(move || {
for i in 0..3 {
tx.send(i).unwrap(); // blocks while the buffer is full
println!(" sent {i}");
}
});
thread::sleep(Duration::from_millis(10));
for _ in 0..3 {
println!(" received {}", rx.recv().unwrap());
}
producer.join().unwrap();
let (tx, rx) = mpsc::sync_channel::<i32>(1);
tx.try_send(1).unwrap();
println!("try_send into full buffer: {:?}", tx.try_send(2));
println!("drain {:?}", rx.try_iter().collect::<Vec<_>>());
}try_recv and recv_timeout
fn demo_non_blocking() {
println!("-- try_recv / recv_timeout --");
let (tx, rx) = mpsc::channel();
println!("try_recv empty: {:?}", rx.try_recv() == Err(TryRecvError::Empty));
tx.send("hi").unwrap();
println!("try_recv {:?}", rx.try_recv());
println!(
"timeout {:?}",
rx.recv_timeout(Duration::from_millis(5)) == Err(RecvTimeoutError::Timeout)
);
drop(tx);
println!("disconnected {:?}", rx.try_recv() == Err(TryRecvError::Disconnected));
}A worker pool with a job channel and a result channel
enum Job {
Square(u64),
Stop,
}
fn demo_worker_pool() {
println!("-- worker pool --");
let (job_tx, job_rx) = mpsc::channel::<Job>();
let (res_tx, res_rx) = mpsc::channel::<(usize, u64)>();
// Receiver is not Clone; share it behind a Mutex.
let job_rx = std::sync::Arc::new(std::sync::Mutex::new(job_rx));
let workers: Vec<_> = (0..3)
.map(|id| {
let job_rx = std::sync::Arc::clone(&job_rx);
let res_tx = res_tx.clone();
thread::spawn(move || {
loop {
let job = job_rx.lock().unwrap().recv();
match job {
Ok(Job::Square(n)) => res_tx.send((id, n * n)).unwrap(),
Ok(Job::Stop) | Err(_) => break,
}
}
})
})
.collect();
drop(res_tx);
for n in 1..=6 {
job_tx.send(Job::Square(n)).unwrap();
}
for _ in &workers {
job_tx.send(Job::Stop).unwrap();
}
let mut results: Vec<u64> = res_rx.iter().map(|(_, v)| v).collect();
results.sort();
for w in workers {
w.join().unwrap();
}
println!("squares {results:?}");
}A request/response pattern: send a reply channel with the request
fn demo_request_reply() {
println!("-- request / reply --");
let (req_tx, req_rx) = mpsc::channel::<(String, mpsc::Sender<usize>)>();
let server = thread::spawn(move || {
for (text, reply) in req_rx {
reply.send(text.len()).unwrap();
}
});
for word in ["a", "hello"] {
let (reply_tx, reply_rx) = mpsc::channel();
req_tx.send((word.to_string(), reply_tx)).unwrap();
println!("{word} -> {}", reply_rx.recv().unwrap());
}
drop(req_tx);
server.join().unwrap();
}$ cargo run --bin 45_channels -- basic -- ping after sender dropped: Err(RecvError) moved through the channel -- stream -- 10 20 30 -- multiple producers -- 6 messages: ["worker 0 msg 0", "worker 0 msg 1", "worker 1 msg 0", "worker 1 msg 1", "worker 2 msg 0", "worker 2 msg 1"] -- sync_channel -- sent 0 received 0 sent 1 received 1 sent 2 received 2 try_send into full buffer: Err("Full(..)") drain [1] -- try_recv / recv_timeout -- try_recv empty: true try_recv Ok("hi") timeout true disconnected true -- worker pool -- squares [1, 4, 9, 16, 25, 36] -- request / reply -- a -> 1 hello -> 5
Full program
// 45. Channels: mpsc, multiple producers, sync_channel, try_recv
//
// std::sync::mpsc is a multiple-producer, single-consumer channel. channel() returns a
// (Sender, Receiver) pair with an unbounded buffer. send moves the value to the other
// side, transferring ownership between threads. recv blocks until a value arrives, and
// returns Err once every Sender has been dropped and the buffer is empty.
//
// Clone the Sender to get several producers. Iterating over the Receiver (for msg in
// rx) runs until all senders are gone, so drop the original Sender when you are done.
//
// sync_channel(n) has a bounded buffer: send blocks when it is full, which gives a fast
// producer backpressure. sync_channel(0) is a rendezvous where every send waits for a
// recv. try_recv and recv_timeout never block forever.
//
// Run: cargo run --bin 45_channels
use std::sync::mpsc::{self, RecvTimeoutError, TryRecvError};
use std::thread;
use std::time::Duration;
// Example 1: one producer, one consumer.
fn demo_basic() {
println!("-- basic --");
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send("ping").unwrap();
});
println!("{}", rx.recv().unwrap()); // blocks until ping arrives
println!("after sender dropped: {:?}", rx.recv());
let (tx, rx) = mpsc::channel();
let s = String::from("moved through the channel");
tx.send(s).unwrap();
// println!("{s}"); // error[E0382]: borrow of moved value: `s`
println!("{}", rx.recv().unwrap());
}
// Example 2: a stream of values, consumed with a for loop.
fn demo_stream() {
println!("-- stream --");
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
for i in 1..=3 {
tx.send(i * 10).unwrap();
thread::sleep(Duration::from_millis(1));
}
}); // tx dropped here, which ends the loop below
for n in rx {
print!("{n} ");
}
println!();
}
// Example 3: multiple producers.
fn demo_multi_producer() {
println!("-- multiple producers --");
let (tx, rx) = mpsc::channel();
for id in 0..3 {
let tx = tx.clone();
thread::spawn(move || {
for j in 0..2 {
tx.send(format!("worker {id} msg {j}")).unwrap();
}
});
}
drop(tx); // without this, the loop below would wait forever
let mut got: Vec<String> = rx.iter().collect();
got.sort();
println!("{} messages: {got:?}", got.len());
}
// Example 4: bounded channels and backpressure.
fn demo_sync_channel() {
println!("-- sync_channel --");
let (tx, rx) = mpsc::sync_channel(1);
let producer = thread::spawn(move || {
for i in 0..3 {
tx.send(i).unwrap(); // blocks while the buffer is full
println!(" sent {i}");
}
});
thread::sleep(Duration::from_millis(10));
for _ in 0..3 {
println!(" received {}", rx.recv().unwrap());
}
producer.join().unwrap();
let (tx, rx) = mpsc::sync_channel::<i32>(1);
tx.try_send(1).unwrap();
println!("try_send into full buffer: {:?}", tx.try_send(2));
println!("drain {:?}", rx.try_iter().collect::<Vec<_>>());
}
// Example 5: try_recv and recv_timeout.
fn demo_non_blocking() {
println!("-- try_recv / recv_timeout --");
let (tx, rx) = mpsc::channel();
println!("try_recv empty: {:?}", rx.try_recv() == Err(TryRecvError::Empty));
tx.send("hi").unwrap();
println!("try_recv {:?}", rx.try_recv());
println!(
"timeout {:?}",
rx.recv_timeout(Duration::from_millis(5)) == Err(RecvTimeoutError::Timeout)
);
drop(tx);
println!("disconnected {:?}", rx.try_recv() == Err(TryRecvError::Disconnected));
}
// Example 6: a worker pool with a job channel and a result channel.
enum Job {
Square(u64),
Stop,
}
fn demo_worker_pool() {
println!("-- worker pool --");
let (job_tx, job_rx) = mpsc::channel::<Job>();
let (res_tx, res_rx) = mpsc::channel::<(usize, u64)>();
// Receiver is not Clone; share it behind a Mutex.
let job_rx = std::sync::Arc::new(std::sync::Mutex::new(job_rx));
let workers: Vec<_> = (0..3)
.map(|id| {
let job_rx = std::sync::Arc::clone(&job_rx);
let res_tx = res_tx.clone();
thread::spawn(move || {
loop {
let job = job_rx.lock().unwrap().recv();
match job {
Ok(Job::Square(n)) => res_tx.send((id, n * n)).unwrap(),
Ok(Job::Stop) | Err(_) => break,
}
}
})
})
.collect();
drop(res_tx);
for n in 1..=6 {
job_tx.send(Job::Square(n)).unwrap();
}
for _ in &workers {
job_tx.send(Job::Stop).unwrap();
}
let mut results: Vec<u64> = res_rx.iter().map(|(_, v)| v).collect();
results.sort();
for w in workers {
w.join().unwrap();
}
println!("squares {results:?}");
}
// Example 7: a request/response pattern: send a reply channel with the request.
fn demo_request_reply() {
println!("-- request / reply --");
let (req_tx, req_rx) = mpsc::channel::<(String, mpsc::Sender<usize>)>();
let server = thread::spawn(move || {
for (text, reply) in req_rx {
reply.send(text.len()).unwrap();
}
});
for word in ["a", "hello"] {
let (reply_tx, reply_rx) = mpsc::channel();
req_tx.send((word.to_string(), reply_tx)).unwrap();
println!("{word} -> {}", reply_rx.recv().unwrap());
}
drop(req_tx);
server.join().unwrap();
}
fn main() {
demo_basic();
demo_stream();
demo_multi_producer();
demo_sync_channel();
demo_non_blocking();
demo_worker_pool();
demo_request_reply();
}