bigywater.tech
Rust · 88

Channel 消息传递

“不要通过共享内存来通信,而要通过通信来共享内存。”通道到底怎么工作?send 后原变量还能用吗?什么时候 recv 会结束?值有没有被复制?这篇把通道最基本的行为逐条验证。

Disconnected
所有发送端 drop 之后 try_recv / recv_timeout 的错误
0 次复制
发送 100 万个 u64 的 Vec 时数据的复制次数(地址不变)
E0382
send 之后再使用原变量

send 与 recv

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    recv 阻塞了 100 毫秒(取整到 50),收到 "来自子线程"
  源码:
      1 | use std::sync::mpsc;
      2 | fn main() { let (tx, rx) = mpsc::channel(); let s = String::from("x"); tx.send(s).unwrap(); println!("{}", s); let _ = rx; }
  编译:失败
    error[E0382]: borrow of moved value: `s`
     --> <实验目录>/src/u88b.rs:2:108
      |
    2 | fn main() { let (tx, rx) = mpsc::channel(); let s = String::from("x"); tx.send(s).unwrap(); println!("{}", s); let _ = rx; }
      |                                                 -                              -                           ^ value borrowed here after move
      |                                                 |                              |
      |                                                 |                              value moved here
      |                                                 move occurs because `s` has type `String`, which does not implement the `Copy` trait
      |
    help: consider cloning the value if the performance cost is acceptable
      |
    2 | fn main() { let (tx, rx) = mpsc::channel(); let s = String::from("x"); tx.send(s.clone()).unwrap(); println!("{}", s); let _ = rx; }
      |                                                                                 ++++++++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0382`.

通道什么时候关闭

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    子线程结束,tx 随之 drop
    for / iter 收完后结束,共收到 [1, 2, 3]
    再 recv: Err(RecvError)

不阻塞的接收

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    空通道 try_recv: Err(Empty)
    recv_timeout(50ms): Err(Timeout)
    有值时 try_recv: Ok(1)
    发送端都 drop 后 try_recv: Err(Disconnected)
    发送端都 drop 后 recv_timeout: Err(Disconnected)

接收端关闭

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    send 失败: sending on a closed channel  被退回的值: "没人收"

能传什么类型

实测 · 本机执行输出
  源码:
      1 | use std::{sync::mpsc, rc::Rc, thread};
      2 | fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { tx.send(Rc::new(1)).unwrap(); }); let _ = rx.recv(); }
  编译:失败
    error[E0277]: `Rc<i32>` cannot be sent between threads safely
       --> <实验目录>/src/u88f.rs:2:59
        |
      2 | fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { tx.send(Rc::new(1)).unwrap(); }); let _ = rx.recv(); }
        |                                             ------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Rc<i32>` cannot be sent between threads safely
        |                                             |
        |                                             required by a bound introduced by this call
        |
        = help: the trait `Send` is not implemented for `Rc<i32>`
        = note: required for `std::sync::mpsc::Sender<Rc<i32>>` to implement `Send`
    note: required because it's used within this closure
       --> <实验目录>/src/u88f.rs:2:59
        |
      2 | fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { tx.send(Rc::new(1)).unwrap(); }); let _ = rx.recv(); }
        |                                                           ^^^^^^^
    note: required by a bound in `spawn`
       --> <rust 标准库源码>/library/std/src/thread/functions.rs:128:8
        |
    125 | pub fn spawn<F, T>(f: F) -> JoinHandle<T>
        |        ----- required by a bound in this function
    ...
    128 |     F: Send + 'static,
        |        ^^^^ required by this bound in `spawn`
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.

Send 的规则见Send 与 Sync。

零复制

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    发送前缓冲区地址 == 接收后缓冲区地址: true(100 万个 u64 没有被复制)

多个生产者的情形见mpsc 多生产者单消费者。

分步演示

通道
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动
    • 没有验证:通道内部的数据结构(链表还是数组)、无界通道在生产快于消费时的内存增长、select 式多通道等待(标准库没有,放在 async 部分)、crossbeam-channel 的性能对比
    京ICP备2026067502号-1京公网安备11010502063487号