Rust多线程编程之Thread与Channel

Rust利用所有权(Ownership)系统,在编译时保证线程安全,从根本上杜绝了数据竞争(Data Race);其多线程编程主要:

  • std::thread:用于创建和管理线程。
  • std::sync::mpsc:消息传递(Message Passing),使用 Channel(通道)进行线程间的通信和同步。

线程std::thread

Rust 的标准库使用 1:1 线程模型,即一个 Rust 线程直接对应一个操作系统线程。

创建spawn

使用 thread::spawn 函数来创建一个新线程,其接收一个闭包作为线程执行体,返回 JoinHandle<T>T 是闭包返回值类型)。

  • join:等待线程执行完成;
  • detach:分离线程(变为后台线程,由操作系统接管);在无需等待线程时使用;

使用thread::Builder可设置线程一些信息(线程名、栈大小等)。

若在新线程中使用主线程的变量,必须将这些变量的所有权转移(Move)给新线程。

rust 复制代码
use std::thread;

fn main() {
    let v = vec![1, 2, 3];

    let builder = thread::Builder::new()
    .name("channel-test".to_string())
    .stack_size(4 * 1024 * 1024);

    // 必须使用 move 关键字,将 v 的所有权强制转移到闭包内
    // let handle = thread::spawn(move || {
    let handle = builder.spawn(move || {
        println!("向量 v: {:?}", v);
        thread::sleep(Duration::from_millis(10));
    });
    
    // 此时 v 在主线程中已不可用
    // println!("{:?}", v); // 这一行如果取消注释会报错

    handle.join().unwrap();
}

线程间通信Channel

线程间通信(IPC)是多线程协作的核心,Rust 基于 CSP(通信顺序进程) 模型提供了 mpsc 通道(Multiple Producer Single Consumer;多生产者、单消费者):

  • Sender<T>:发送端,可克隆(支持多生产者);通过 send 发送数据。
  • Receiver<T>:接收端,不可克隆(单消费者)
    • 通过 recv/try_recv/recv_timeout 接收数据。
    • Receiver 实现了迭代器 trait,可直接遍历。

mpsc::channel() 默认创建的是无界(unbounded)缓冲区通道,发送端 send 不会阻塞。数据通过 Channel 传递时,所有权会从 Sender 转移到 Receiver,天然避免数据竞争(无需手动同步)。

mpsc::sync_channel(n) 创建的则是带容量限制通道:

  • 当通道内数据量达到容量 n 时,send 会阻塞,直到有数据被接收。
  • 若容量为 0,则 send 必须等待 recv 调用(严格同步)

多生产者(克隆 Sender)示例

rust 复制代码
use std::thread;
use std::sync::mpsc;
use std::time::Duration;

pub fn run_mpsc_clone() {
    let (tx, rx) = mpsc::channel();
    let tx1 = tx.clone(); // 克隆发送端,支持多线程发送

    // 线程 1 发送
    thread::spawn(move || {
        let vals = vec![
            String::from("消息1"),
            String::from("消息2"),
            String::from("消息3"),
        ];

        for val in vals {
            tx.send(format!("线程 1: {}",val)).unwrap();
            thread::sleep(Duration::from_secs(1));
        }
    });

    // 线程 2 发送
    thread::spawn(move || {
        let vals = vec![
            String::from("消息1"),
            String::from("消息2"),
            String::from("消息3"),
        ];

        for val in vals {
            tx1.send(format!("线程 2: {}",val)).unwrap();
            thread::sleep(Duration::from_secs(1));
        }
    });

    // 接收所有消息(迭代器方式,直到所有 Sender 关闭)
    for received in rx {
        println!("收到: {}", received);
    }
}

channel错误处理

sendrecv 均返回 Result<T, SendError<T>>/Result<T, RecvError>,错误场景:

  • send 失败:所有 Receiver 已被销毁(通道关闭)。
  • recv 失败:所有 Sender 已被销毁(无更多数据发送)。

多消费者

std::sync::mpsc 只提供单消费者。要实现多消费者:

  • 使用 crossbeam-channel crate(性能高且功能强大)
  • 使用 Arc<Mutex<Receiver>> 包裹标准库的 mpsc::Receiver(不推荐,因为锁会导致性能下降)。

crossbeam-channel实现的是竞争消费(Work Stealing/Load Balancing):一条消息只能被其中一个消费者抢到,而不是广播给所有人。

多消费者(receiver也可clone):

plain 复制代码
use std::thread;
use std::sync::mpsc;
use std::time::Duration;
use crossbeam_channel::unbounded;

pub fn run_mpmc() {
    // 创建一个无界通道。
    // s: Sender, r: Receiver
    let (s, r) = unbounded();

    // --- 1. 创建多个消费者 (Consumers) ---
    for i in 0..3 {
        // 关键点:Receiver 可以直接 clone!
        let r_clone = r.clone();
        thread::spawn(move || {
            // 像迭代器一样接收消息
            for msg in r_clone {
                println!("消费者 [{}] 抢到了任务: {}", i, msg);
                // 模拟处理耗时,观察负载均衡效果
                thread::sleep(Duration::from_millis(500));
            }
        });
    }

    // --- 2. 创建多个生产者 (Producers) ---
    for i in 0..3 {
        let s_clone = s.clone();
        thread::spawn(move || {
            for j in 1..=5 {
                let msg = format!("P{}-任务{}", i, j);
                s_clone.send(msg).unwrap();
                thread::sleep(Duration::from_millis(200));
            }
        });
    }

    // 主线程等待一会儿观察结果
    // 注意:crossbeam 的 Receiver 不会阻塞主线程退出,这里强行 sleep 演示
    thread::sleep(Duration::from_secs(3));
    println!("演示结束");
}

广播模式

crossbeam-channel一条消息只能被消费一次,以下三方库提供广播:

  • Tokio (异步): 使用 tokio::sync::broadcast
  • 同步多线程: 使用 bus crate 或者 postage crate。
plain 复制代码
use std::thread;
use bus::Bus;

pub fn run_bus() {
    let mut bus = Bus::new(10); // 容量 10

    // 创建两个接收者
    let mut rx1 = bus.add_rx();
    let mut rx2 = bus.add_rx();

    thread::spawn(move || {
        // recv 是阻塞的
        println!("消费者 1 收到: {}", rx1.recv().unwrap());
    });

    thread::spawn(move || {
        println!("消费者 2 收到: {}", rx2.recv().unwrap());
    });

    // 广播消息
    bus.broadcast("bus-send message!".to_string());
    thread::sleep(std::time::Duration::from_secs(1));
}
相关推荐
星释9 小时前
Rust 练习册 95:React与响应式编程
开发语言·react.js·rust
Eighteenzi9 小时前
tokio 的理解
rust
星释10 小时前
Rust 练习册 96:Rectangles与几何计算
开发语言·后端·rust
星释10 小时前
Rust 练习册 97:Run-Length Encoding 压缩算法
java·linux·rust
肖祥10 小时前
Actix-Web完整项目实战:博客 API
rust
alwaysrun10 小时前
Rust中的模式匹配
rust·match·绑定·模式匹配·解构·守卫模式
A***071710 小时前
Rust在网络中的Actix Web
开发语言·后端·rust
王燕龙(大卫)11 小时前
rust:trait
开发语言·后端·rust
q***876016 小时前
项目升级Sass版本或升级Element Plus版本遇到的问题
前端·rust·sass