并发编程
Rust 致力于实现并发编程的安全和高效.
并发编程实际是两个概念组成:
- 并发: 程序的不同部分独立运行(concurrent)
- 并行: 程序的不同部分同时运行(parallel)
由于目前多核 CPU 的广泛应用, 并行处理越来越重要.(下面说的并发实际是并发和并行的统称)
Rust 通过独特的 Ownership 和 Borrow check 以及类型系统来保证并发编程的安全和高效. 且错误都是在编译时就暴露出来了, 因此在 Rust 中有 fearless concurrency
的说法.
不同的语言提供来并行编程的相关工具, 比如 Erlang 的消息传递, Go 的 channel 等. Rust 也提供来一系列的工具用于并行编程:
- 创建平台线程
- 消息传递 channel
- 内存共享
- Sync 和 Send trait.
使用线程同时运行代码
提升程序性能的一个最直接的方式是: 通过线程将程序中的计算分解并同时进行. 但这个方式会增加程序的复杂性, 同时也会引入一些问题:
- 竞争条件: 不同线程同时访问同一资源时顺序不一致造成的结果不一致.
- 死锁: 两个或多个线程都在等待对方(形成环形依赖关系), 从而导致这些线程均不能继续运行.
- 出现一些无法重现或很难重现的 bug, 因为这些 bug 是在特定多线程环境下才会出现的.
不同的编程语言的线程实现方式也各不相同, 不过绝大部分操作系统都提供有系统 API 用于创建线程. Rust 标准库使用的是 1:1 的线程实现模型, 即 Rust 中创建一个线程就等同于使用系统 API 创建一个线程. 当然也有三方的 Rust crate 提供其它的一些线程模型, 比如 1:N 的.
创建和使用线程
可以使用 thread::spawn
函数来创建线程, 并提供一个表示需要执行任务的闭包:
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
可以通过 join
来等待所有线程完成:
for i in 1..10 {
let handle = thread::spawn(|| {
println!("num is {}", i);
});
handle.join().unwrap();
}
使用 move
将上下文值所有权传入线程闭包中:
use std::thread;
fn main() {
let v = vec![1, 2, 3];
// 如果不使用 move, 则 v 的引用可能在 main 函数执行的主线程中被释放掉.
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
}