Rust std::sync::mpsc:用通道转移所有权通信
更新时间:2026-09-20。Rust 并发的口号是「不要通过共享内存来通信,而要通过通信来共享内存」。标准库提供多生产者、单消费者(multi-producer, single-consumer)通道。无锁队列四象限的系统视角见 消息队列四象限,共享内存方案见 Arc/Mutex。
一、基本:单生产者 → 单消费者
rust
use std::sync::mpsc;
use std::thread;
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send(String::from("hello")).unwrap(); // send 拿走所有权
});
let msg = rx.recv().unwrap(); // 阻塞等下一条
println!("{msg}");send(v) 把 v 的所有权转移给接收端,发送后原变量不可再用——编译器据此保证不会有两个线程同时碰这个值。
二、多生产者:clone 发送端
Receiver 只有一个,但 Sender 可以 clone,每个生产者一份:
rust
let (tx, rx) = mpsc::channel();
for i in 0..3 {
let tx = tx.clone();
thread::spawn(move || tx.send(i).unwrap());
}
drop(tx); // 主线程这份原始 tx 也要 drop,否则通道永不关闭
for v in rx { println!("{v}"); } // 所有 tx drop 后,迭代自然结束三、关闭语义:recv 何时返回 Err
- 通道里还有消息:
recv照常取出。 - 消息取空、且所有
Sender都已 drop:recv返回Err(RecvError),for v in rx结束。 - 只要还有一个 tx 活着,
recv就继续阻塞等待。因此忘了 drop 多余的 tx,消费者会永远挂住,这是最常见的坑。
四、接收方式与同步通道
| 写法 | 行为 |
|---|---|
rx.recv() | 阻塞直到有值或通道关闭 |
rx.try_recv() | 不阻塞,空时返回 Err(TryRecvError::Empty),可轮询 |
rx.recv_timeout(d) | 等最多 d,超时返回 Err(Timeout) |
for v in rx | 阻塞迭代到关闭 |
rust
// 有界同步通道:容量 0/有限,send 在缓冲区满时阻塞,天然背压
let (tx, rx) = mpsc::sync_channel(2);普通 channel 是无界的(发送不阻塞、生产过快会堆积内存);要限制在途消息用 sync_channel(n),n=0 即 rendezvous(发送方阻塞到接收方取走)。
五、常见坑
- 忘了 drop 原始/多余的 tx:
for v in rx或最后一次recv永不结束。 - 想多个线程共用 rx:
Receiver不是 Sync,不能 clone 共享;需要 MPMC 用crossbeam-channel/flume。 - send 后继续用值:所有权已移交,编译报错。
- 无界通道当背压队列:生产快于消费会 OOM,用
sync_channel或第三方有界通道。 - 通道里传非 Send 类型(如
Rc):编译失败,换Arc。 - 把
recv放主线程无限阻塞:配合recv_timeout或 shutdown 信号,避免无法优雅退出。
总结
std::sync::mpsc 提供多生产者单消费者通道,channel() 返回 (tx,rx),send 转移值的所有权、recv 阻塞取值,克隆 tx 即可让多个线程生产、而 rx 唯一不可共享;通道在所有 Sender 都 drop 后才关闭,recv 随之返回 Err、for 迭代结束,所以务必 drop 掉不再使用的发送端,否则消费者永久挂起;普通 channel 无界不背压,要用 sync_channel(n) 限制在途消息,需要多消费者或更丰富特性则上 crossbeam-channel/flume。