Rust并发编程--消息传递

各种语言中在多线程间有多种方式可以共享、传递数据,Rust中最常用的方式就是通过消息传递或者将锁和Arc联合使用,而对于前者,在编程界还有一个大名鼎鼎的Actor线程模型为其背书,典型的有 Erlang 语言,还有 Go 语言中很经典的一句话:

Do not communicate by sharing memory; instead, share memory by communicating

而后者需要在多线程编程中,保证同步性,当你需要同时访问一个资源、控制不同线程的执行次序时,都需要使用到同步性。这个会在后面的介绍,先说一下消息如何在多线程传递。

// 多/单发送者,单接受者
// 标准库提供了通道std::sync::mpsc,其中mpsc是multiple producer, single consumer的缩写,代表了该通道支持多个发送者,但是只支持唯一的接收者。 
// 当然,支持多个发送者也意味着支持单个发送者,我们先来看看单发送者、单接收者的简单例子
use std::sync::mpsc::{self, Receiver, Sender};
use std::thread;
use std::time::Duration;

fn mpsc_demo() {
     // 创建一个消息通道, 返回一个元组:(发送者,接收者)
     let (tx, rx) = mpsc::channel();

     // 创建线程,并发送消息
     thread::spawn(move || {  // 使用move将tx的所有权转移到子线程的闭包
         // 发送一个数字1, send方法返回Result<T,E>,通过unwrap进行快速错误处理
         tx.send(1).unwrap();
         tx.send(2).unwrap();  // 发几个接几个
 
         // 下面代码将报错,因为编译器自动推导出通道传递的值是i32类型,那么Option<i32>类型将产生不匹配错误
         // tx.send(Some(1)).unwrap()
     });
 
     // 在主线程中接收子线程发送的消息并输出,接收消息的操作rx.recv()会阻塞当前线程,直到读取到值,或者通道被关闭
     println!("receive {}", rx.recv().unwrap());  // 1
     println!("receive {}", rx.recv().unwrap());  // 2
    // println!("receive {:?}", rx.try_recv());  // 不阻塞,由于子线程的创建需要时间,因此println!和try_recv方法会先执行,而此时子线程的消息还未被发出。try_recv会尝试立即读取一次消息,因为消息没有发出,此次读取最终会报错
}

// 传递所有权数据
// - 若值的类型实现了Copy特征,则直接复制一份该值,然后传输过去,例如之前的i32类型
// - 若值没有实现Copy,则它的所有权会被转移给接收端,在发送端继续使用该值将报错
fn pass_owner() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let s = String::from("我,飞走咯!");
        tx.send(s).unwrap();
        //println!("val is {}", s);
    });

    let received = rx.recv().unwrap();
    // String底层的字符串是存储在堆上,并没有实现Copy特征,当它被发送后,会将所有权从发送端的s转移给接收端的received,之后s将无法被使用
    println!("Got: {}", received);
}

// 上面讲的都是异步通道,无论接收者是否正在接收消息,消息发送者在发送消息时都不会阻塞。
// 下面说一下同步通道
// 与异步通道相反,同步通道发送消息是阻塞的,只有在消息被接收后才解除阻塞,例如
fn sync_chan() {
    let (tx, rx)= mpsc::sync_channel(0);  // 消息缓冲
    // 该值可以用来指定同步通道的消息缓存条数,当你设定为N时,发送者就可以无阻塞的往通道中发送N条消息,当消息缓冲队列满了后,新的消息发送将被阻塞

    let handle = thread::spawn(move || {
        println!("发送之前");
        tx.send(1).unwrap();
        println!("发送之后");
    });

    println!("睡眠之前");
    thread::sleep(Duration::from_secs(3));
    println!("睡眠之后");

    println!("receive {}", rx.recv().unwrap());
    handle.join().unwrap();
    // 睡眠之前
    // 发送之前
    // ···睡眠3秒
    // 睡眠之后
    // receive 1
    // 发送之后
}

// 关闭通道,所有发送者被drop或者所有接收者被drop后,通道会自动关闭。

// 传输多种数据类型的消息通道
// 一个消息通道只能传输一种类型的数据,如果你想要传输多种类型的数据,可以为每个类型创建一个通道,你也可以使用枚举类型来实现
fn send_enum() {
    enum Fruit {
        Apple(u8),
        Orange(String)
    }

    let (tx, rx): (Sender<Fruit>, Receiver<Fruit>) = mpsc::channel();

    tx.send(Fruit::Orange("sweet".to_string())).unwrap();
    tx.send(Fruit::Apple(2)).unwrap();

    for _ in 0..2 {
        match rx.recv().unwrap() {
            Fruit::Apple(count) => println!("received {} apples", count),
            Fruit::Orange(flavor) => println!("received {} oranges", flavor),
        }
    }
}

// 容易掉进去的坑
fn drop_send() {
    let (send, recv) = mpsc::channel();
    let num_threads = 3;

    for i in 0..num_threads {  // 起3个线程发送消息
        let thread_send = send.clone();  // 由于每个send都进行了clone,导致原有的send没有被所有权转移,并drop
        thread::spawn(move || {
            thread_send.send(i).unwrap();
            println!("thread {:?} finished", i);
        });
    }

    // 在这里drop send...
    // 通道关闭的两个条件:发送者全部drop或接收者被drop
    drop(send);  // 自己没有被move掉

    for x in recv {
        println!("Got: {}", x);
    }
    println!("finished iterating");
}

fn main() {

    mpsc_demo();
    pass_owner();

    // for 循环接受和发送
    let (tx, rx) = mpsc::channel();
    let tx1 = tx.clone();  // 多发送者

    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_secs(1));
        }
    });

    thread::spawn(move || {
        tx1.send(String::from("hi from cloned tx")).unwrap();
    });
    //由于两个子线程谁先创建完成是未知的,因此哪条消息先发送也是未知的,最终主线程的输出顺序也不确定
    for received in rx {
        println!("Got: {}", received);
    }

    sync_chan();
    send_enum();
}


// 如果你需要 mpmc(多发送者,多接收者)或者需要更高的性能,可以考虑第三方库:
    // - crossbeam-channel, 老牌强库,功能较全,性能较强,之前是独立的库,但是后面合并到了crossbeam主仓库中
    // - flume, 官方给出的性能数据某些场景要比 crossbeam 更好些

本Rust问道系列文章都是基于学习《Rust圣经》文章而来,在此感谢 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值