Rust
的并发
- 进程process -> 线程threads,存在的问题:
- 竞争状态(Race conditions),多个线程以不一致的顺序访问数据或资源
- 死锁(Deadlocks),两个线程相互等待对方停止使用其所拥有的资源,这会阻止它们继续运行
- 只会发生在特定情况且难以稳定重现和修复的 bug
- Rust 标准库只提供了 1:1 线程模型实现(1 个绿色线程对应 1 个 OS 线程);
- 线程安全带有性能惩罚;
- Rust 语言本身对并发
知之甚少
,但并发方案不受标准库或语言所限:我们可以编写自己的或使用别人编写的并发功能,使用两个并发概念 std::marker 中的Sync 和 Send
trait来扩展并发。
使用线程
- 使用spawn 创建线程;
- 使用 join 等待所有线程结束:
handle.join().unwrap();
- 为了在新建线程中使用来自于主线程的数据,需要新建线程的闭包获取主线程中的变量值:使用 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 这样可以安全的用于并发上下文的智能指针。下面是这两种方式的详细描述
并发方式一 消息传递
- 使用消息传递在线程间传送数据。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); }
- 通道的接收端有两个有用的方法:recv 和 try_recv,recv 阻塞,try_recv 不阻塞;
- send 函数获取其参数的所有权并移动这个值归接收者所有;
- rx 也可以作为一个迭代器,当通道被关闭时,迭代器也将结束:
for received in rx { println!("Got: {}", received); }
- 可通过克隆发送者来创建多个生产者
let (tx, rx) = mpsc::channel(); let tx1 = mpsc::Sender::clone(&tx);
并发方式二 共享状态
由于共享状态的复杂难以使用,大多数人更热衷于通道
- 通道都类似于单所有权,因为一旦将一个值传送到通道中,将无法再使用这个值。共享内存类似于多所有权:多个线程可以同时访问相同的内存位置;
- 共享会涉及到两个智能指针:
互斥器(Mutex<T>) 和 原子引用计数(Arc<T>)
- 互斥器
Mutex<T>
- 互斥器一次只允许一个线程访问数据
- 互斥器以难以使用著称,因为你不得不记住:
- 在使用数据之前尝试获取锁。
- 处理完被互斥器所保护的数据之后,必须解锁数据,这样其他线程才能够获取锁。
- 互斥器的使用:
let m = Mutex::new(5);
- 这个调用会阻塞当前线程,直到我们拥有锁为止;
- 一旦获取了锁,就可以将返回值(在这里是num)视为一个其内部数据的可变引用;
- Mutex 是一个智能指针,实现了 Deref 和 Drop,锁的释放是自动发生
- 使用 Mutex 在多个线程间共享值,涉及到 Arc。
- Mutex 也有造成 死锁(deadlock) 的风险。这发生于当一个操作需要锁住两个资源而两个线程各持一个锁,这会造成它们永远相互等待
- 原子引用计数
Arc<T>
- 因为多个线程中不能同时共享一个值,所以涉及到多线程和多所有权:Rc。但Rc 并不能安全的在线程间共享,我们使用原子引用计数 Arc,原子性类型工作起来类似原始类型。
- 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
- 通过 Send 允许在线程间转移所有权
- 几乎所有的 Rust 类型都是Send, 但 Rc 除外
- Sync 允许多线程访问(安全的引用)
- Sync 标记 trait 表明一个实现了 Sync 的类型可以安全的在多个线程中拥有其值的引用:对于任意类型 T,如果 &T(T 的引用)是 Send 的话 T 就是 Sync 的,这意味着其引用就可以安全的发送到另一个线程, Rc 也不是 Sync。
- 手动实现 Send 和 Sync 是不安全的