Rust 并发编程

在现代操作系统中, 激活的程序运行在一个进程(process)中, 操作系统负责管理这些进程.在程序内部, 可以拥有多个同时运行的独立部分, 他们被称为线程(thread).

将任务放在不同的线程中执行可以改善程序的性能, 但也会增加程序的复杂性, 由于thread是同时且独立执行的, 因此无法预先保证代码的执行顺序. 由此会产生一些问题;

  • 竞态条件: 多个线程以不一致的顺序访问统一数据或资源.
  • 死锁: 线程之间互相等待对方释放资源
  • 特定运行状态下的难以复现和修复的问题.

很多操作系统提供了创建新线程的API, 这种由编程语言调用系统API创建线程的模型被称为1:1 模型, 一个系统线程对应着一个语言线程.

很多编程语言也有提供独特的方式创建线程, 由编程语言提供的线程被称为绿色线程, 使用绿色线程的语言会在不同数量的系统线程上执行, 这种模式被称为M:N模型, M个系统线程对应着N个语言线程.

绿色线程的M:N模型需要更大的语言运行时(runtime)来支持和管理这些线程. Rust标准库为了保持高性能以及更小的运行时, 只提供了1:1模型的实现. 而M:N模型则通过三方crate 交由社区实现, 比如 Tokio .

Try in playground

Rust展开
1234567891011121314151617181920212223242526
use std::thread;
use std::time::Duration;

fn main(){

    for i in 0..6 {
      thread::spawn(move ||{
          println!("hi from spawn thread: {i}");
      });
    }

    for i in 0..3 {
        println!("hi from main thread: {i}");
        thread::sleep(Duration::from_millis(1));
    };
}
/**********************output**********************/
hi from spawn thread: 0
hi from spawn thread: 1
hi from main thread: 0
hi from spawn thread: 3
hi from spawn thread: 2
hi from spawn thread: 4
hi from spawn thread: 5
hi from main thread: 1
hi from main thread: 2

上述代码的执行结果并不固定, 执行顺序是不确定的. 他们会轮流执行, 这依赖系统对线程的调度.

thread::sleep函数让当前线程暂停一段时间, 这样可以让其他线程得以执行. 可以使用join方法要求操作系统等待子线程结束

当主线程技术时, 整个程序将会结束, 无论子线程是否执行结束, 甚至不能保证子线程会被执行.

这里面也使用到了 move 闭包, 它允许跨线程的数据操作, 它会将数据的所有权从当前线程转移到新线程中.

Rust的闭包会捕获上下文的数据, 这里是变量 i , 但Rust不清楚性线程会执行多久, 事实上没有谁能知道(包括操作系统), 因此也就无从知晓 i 的生存期.

Try in playground

Rust展开
1234567891011121314151617181920212223242526272829303132
use std::thread;

fn main(){
  let i = vec![1,2,3];
  let th = thread::spawn(||{
      println!("hi from spawn thread: {i:?}");
  });

  th.join().unwrap();
}
/*********************output**********************/
error[E0373]: closure may outlive the current function, but it borrows `i`, which is owned by the current function
 --> src/main.rs:5:26
  |
5 |   let th = thread::spawn(||{
  |                          ^^ may outlive borrowed value `i`
6 |       println!("hi from spawn thread: {i:?}");
  |                                        - `i` is borrowed here
  |
note: function requires argument type to outlive `'static`
 --> src/main.rs:5:12
  |
5 |     let th = thread::spawn(||{
  |  ____________^
6 | |       println!("hi from spawn thread: {i:?}");
7 | |   });
  | |____^
help: to force the closure to take ownership of `i` (and any other referenced variables), use the `move` keyword
  |
5 |   let th = thread::spawn(move ||{
  |                          ++++

如果使用了move闭包则数据将会转移到新线程中, 主线程将不能再使用这个数据

Try in playground

Rust展开
12345678910111213141516171819202122232425262728
use std::thread;

fn main(){
  let i = vec![1,2,3];
  let th = thread::spawn(move ||{
      println!("hi from spawn thread: {i:?}");
  });

  println!("i = {i:?}");

  th.join().unwrap();
}
/*********************output**********************/
error[E0382]: borrow of moved value: `i`
 --> src/main.rs:9:18
  |
4 |   let i = vec![1,2,3];
  |       - move occurs because `i` has type `Vec<i32>`, which does not implement the `Copy` trait
5 |   let th = thread::spawn(move ||{
  |                          ------- value moved into closure here
6 |       println!("hi from spawn thread: {i:?}");
  |                                        - variable moved due to use in closure
...
9 |   println!("i = {i:?}");
  |                  ^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info)

- roadup -