RUST 笔记(九)

Rust 的并发

  1. 进程process -> 线程threads,存在的问题:
    • 竞争状态(Race conditions),多个线程以不一致的顺序访问数据或资源
    • 死锁(Deadlocks),两个线程相互等待对方停止使用其所拥有的资源,这会阻止它们继续运行
    • 只会发生在特定情况且难以稳定重现和修复的 bug
  2. Rust 标准库只提供了 1:1 线程模型实现(1 个绿色线程对应 1 个 OS 线程);
  3. 线程安全带有性能惩罚;
  4. Rust 语言本身对并发知之甚少,但并发方案不受标准库或语言所限:我们可以编写自己的或使用别人编写的并发功能,使用两个并发概念 std::marker 中的 Sync 和 Send trait来扩展并发。

使用线程

  1. 使用spawn 创建线程;
  2. 使用 join 等待所有线程结束:handle.join().unwrap();
  3. 为了在新建线程中使用来自于主线程的数据,需要新建线程的闭包获取主线程中的变量值:使用 move 关键字强制闭包获取其使用的环境值的所有权
    fn main() {
        let v = vec![1, 2, 3];
    
        let handle = thread::spawn(move || {
            println!("Here's a vector: {:?}", v);
        });
    
        handle.join().unwrap();
    }
    

Rust 提供了用于消息传递的通道,和像 Mutex 和 Arc 这样可以安全的用于并发上下文的智能指针。下面是这两种方式的详细描述

并发方式一 消息传递
  1. 使用消息传递在线程间传送数据。Rust 中一个实现消息传递并发的主要工具是通道(channel),编程中的通道有两部分组成,一个发送者(transmitter)和一个接收者(receiver)
    fn main() {
        let (tx, rx) = mpsc::channel();// mpsc 是多个发送者,一个接收者
    
        thread::spawn(move || {
            let val = String::from("hi");
            tx.send(val).unwrap();
        });
    
        let received = rx.recv().unwrap(); // 阻塞等待,直到接收到一个消息
        println!("Got: {}", received);
    }
    
  2. 通道的接收端有两个有用的方法:recv 和 try_recv,recv 阻塞,try_recv 不阻塞;
  3. send 函数获取其参数的所有权并移动这个值归接收者所有;
  4. rx 也可以作为一个迭代器,当通道被关闭时,迭代器也将结束:
        for received in rx {
            println!("Got: {}", received);
        }
    
  5. 可通过克隆发送者来创建多个生产者
    let (tx, rx) = mpsc::channel();
    
    let tx1 = mpsc::Sender::clone(&tx);
    
并发方式二 共享状态

由于共享状态的复杂难以使用,大多数人更热衷于通道

  • 通道都类似于单所有权,因为一旦将一个值传送到通道中,将无法再使用这个值。共享内存类似于多所有权:多个线程可以同时访问相同的内存位置;
  • 共享会涉及到两个智能指针: 互斥器(Mutex<T>) 和 原子引用计数(Arc<T>)
  1. 互斥器 Mutex<T>
    1. 互斥器一次只允许一个线程访问数据
    2. 互斥器以难以使用著称,因为你不得不记住:
      • 在使用数据之前尝试获取锁。
      • 处理完被互斥器所保护的数据之后,必须解锁数据,这样其他线程才能够获取锁。
    3. 互斥器的使用:let m = Mutex::new(5);
      1. 这个调用会阻塞当前线程,直到我们拥有锁为止;
      2. 一旦获取了锁,就可以将返回值(在这里是num)视为一个其内部数据的可变引用;
      3. Mutex 是一个智能指针,实现了 Deref 和 Drop,锁的释放是自动发生
      4. 使用 Mutex 在多个线程间共享值,涉及到 Arc。
      5. Mutex 也有造成 死锁(deadlock) 的风险。这发生于当一个操作需要锁住两个资源而两个线程各持一个锁,这会造成它们永远相互等待
  2. 原子引用计数 Arc<T>
    1. 因为多个线程中不能同时共享一个值,所以涉及到多线程和多所有权:Rc。但Rc 并不能安全的在线程间共享,我们使用原子引用计数 Arc,原子性类型工作起来类似原始类型
    2. Arc 和 Rc 有着相同的 API;
    use std::sync::{Mutex, Arc};
    use std::thread;
    
    fn main() {
        let counter = Arc::new(Mutex::new(0));
        let mut handles = vec![];
    
        for _ in 0..10 {
            let counter = Arc::clone(&counter);
            let handle = thread::spawn(move || {
                let mut num = counter.lock().unwrap();
    
                *num += 1;
            });
            handles.push(handle);
        }
    
        for handle in handles {
            handle.join().unwrap();
        }
    
        println!("Result: {}", *counter.lock().unwrap());
    }
    

使用 Sync 和 Send trait 的可扩展并发

两个内嵌于语言中的并发概念:std::marker 中的 Sync 和 Send trait

  1. 通过 Send 允许在线程间转移所有权
    • 几乎所有的 Rust 类型都是Send, 但 Rc 除外
  2. Sync 允许多线程访问(安全的引用)
    • Sync 标记 trait 表明一个实现了 Sync 的类型可以安全的在多个线程中拥有其值的引用:对于任意类型 T,如果 &T(T 的引用)是 Send 的话 T 就是 Sync 的,这意味着其引用就可以安全的发送到另一个线程, Rc 也不是 Sync。
  3. 手动实现 Send 和 Sync 是不安全的
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值