Rust 学习 14

archive time: 2022-09-25

今天开始学并发的内容了

Rust 的安全性在并发中也有体现

并发

并发 (concurrent),指程序的不同部分之间 独立 运行,这个和 并行 (parallel) 是有区别的

并发强调的是不同部分可以 独立 运行,而并行则强调不同部分 同时 运行

进程和线程

大部分 OS 里,代码运行在 进程 (process) 里,OS 管理多个进程

程序里,各个部分独立运行,运行这些独立部分的就是 线程 (thread)

多线程运行特点:

  • 提升性能表现
  • 增加复杂性,无法保证各个线程执行顺序

可能影响的问题:

  • 竞争状态,线程以不一致的顺序访问数据或资源
  • 死锁1,两个线程彼此等待对方使用完所持有的资源,线程无法继续
  • 容易有一些 神必 bug,难以复现或修复

一般线程的实现方式:

  • 调用系统 API 来创建线程,一个操作系统线程对应一个语言线程
  • 语言自己实现线程,M 个绿色线程2 对应 N 个系统线程

Rust 标准库仅提供 1:1 模型

使用线程

在 Rust 中使用 std::thread 来使用线程,thread::spawn() 来生成线程

spawn() 函数接受一个闭包作为参数,表示要在线程中执行的代码

use std::{thread, time::Duration};

fn main() {
    thread::spawn(|| {
        for i in 1..=10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..=5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}

对应输出

hi number 1 from the main thread!
hi number 1 from the spawned thread!
hi number 2 from the main thread!
hi number 2 from the spawned thread!
hi number 3 from the spawned thread!
hi number 3 from the main thread!
hi number 4 from the spawned thread!
hi number 4 from the main thread!
hi number 5 from the spawned thread!
hi number 5 from the main thread!
hi number 6 from the spawned thread!

注意到,我们手动生成的线程在主线程运行结束后,即便还未运行完毕,依旧被结束了

为此,我们需要一个方法来等待线程执行结束,那就是 join handle

我们使用 spawn() 函数返回的是一个 JoinHandle<T> 类型, 我们调用其方法 join() 可以阻止当前线程的执行,直到对应 handle 执行结束

join() 方法返回的是 Result<T> 类型,需要处理,这里直接使用 unwrap() 跳过

use std::{thread, time::Duration};

fn main() {
    thread::spawn(|| {
        for i in 1..=10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    })
    .join()
    .unwrap();

    for i in 1..=5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}

此时的执行结果是

hi number 1 from the spawned thread!
hi number 2 from the spawned thread!
hi number 3 from the spawned thread!
hi number 4 from the spawned thread!
hi number 5 from the spawned thread!
hi number 6 from the spawned thread!
hi number 7 from the spawned thread!
hi number 8 from the spawned thread!
hi number 9 from the spawned thread!
hi number 10 from the spawned thread!
hi number 1 from the main thread!
hi number 2 from the main thread!
hi number 3 from the main thread!
hi number 4 from the main thread!
hi number 5 from the main thread!

即,先执行完我们的子线程,再执行主线程

但是,更好的做法是

use std::{thread, time::Duration};

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..=10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..=5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }

    handle.join().unwrap();
}

这样,一开始,主线程和子线程是交替的,然后子线程阻塞主线程,不让主线程结束,直到子线程完成

hi number 1 from the main thread!
hi number 1 from the spawned thread!
hi number 2 from the main thread!
hi number 2 from the spawned thread!
hi number 3 from the main thread!
hi number 3 from the spawned thread!
hi number 4 from the main thread!
hi number 4 from the spawned thread!
hi number 5 from the main thread!
hi number 5 from the spawned thread!
hi number 6 from the spawned thread!
hi number 7 from the spawned thread!
hi number 8 from the spawned thread!
hi number 9 from the spawned thread!
hi number 10 from the spawned thread!

线程间的所有权

要使用其他线程的变量或者值,我们需要使用 move 将所有权移交至其他线程,也就是使用 move 闭包

use rand::Rng;
use std::thread;
use std::time::Duration;

fn main() {
    let mut rng = rand::thread_rng();
    let v = rng.gen::<[u8; 5]>();

    println!("{:?}", v);

    let handle = thread::spawn(move || {
        for i in v {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in v {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }

    handle.join().unwrap();
}

对应输出是

[33, 108, 92, 80, 1]
hi number 33 from the main thread!
hi number 33 from the spawned thread!
hi number 108 from the main thread!
hi number 108 from the spawned thread!
hi number 92 from the main thread!
hi number 92 from the spawned thread!
hi number 80 from the main thread!
hi number 80 from the spawned thread!
hi number 1 from the main thread!
hi number 1 from the spawned thread!

这里,我们使用 move 的方式使得子线程可以使用主线程的变量 v


好,今天就学到这里了,明天学习消息传递机制,摸了

1

Wikipedia.Deadlock [DB/OL]. https://en.wikipedia.org/wiki/Deadlock, 2022-08-25/2022-09-25

2

Wikipedia.Green_thread [DB/OL]. https://en.wikipedia.org/wiki/Green_thread, 2022-08-20/2022-09-25