跳转至

并发基础

线程

与 C++ 的 std::thread::thread 类似, Rust 也可以通过函数 [std::thread::spawn] 来创建线程.

返回的 JoinHandle 在被释放后会分离, 如果此时主线程结束, 也将导致线程被强制结束. 可以通过调用其 join 函数来堵塞调用者线程, 并等待该线程结束.

use std::thread;

fn main() {
    let t1 = thread::spawn(f);
    let t2 = thread::spawn(f);

    println!("Main thread");

    t1.join().unwrap();
    t2.join().unwrap();
}

fn f() {
    let id = thread::current().id();
    println!("Thread id: {id:?}");
}
Main thread
Thread id: ThreadId(2)
Thread id: ThreadId(3)

Rust 的 println 宏与 C++ 的 cout 一样的同步的, 所以即使在并发执行, 也能保证内容完整 (不被打断) 的输出.

一种常见的做法是向传递一个闭包:

let numbers = vec![1, 2, 3];

thread::spawn(move || {
    for n in &numbers {
        println!("{n}");
    }
}).join().unwrap();

如果闭包需要访问外部变量则必须使用 move 关键字将变量的所有权移交给闭包.
如果缺省 move, 闭包将默认通过引用访问外部变量, 但闭包的作用域和外部变量不一致.

上面代码创建的线程立即调用了 join 函数, 所以不存在外部变量被提前释放的情况. 但 Rust 编译器无法识别这一点, 后续会介绍作用域线程, 可以解决这一问题.

std::fs::OpenOptions 类似, 线程也可以通过 [std::thread::Builder] 创建. 该方法可以在线程生成前进行一些设置, 比如设置线程的名字, 后续可以在线程内通过 std::thread::current().name() 获取.

[std::thread::spawn] 函数的实现便是通过调用 [std::thread::Builder], 并使用默认的设置来生成线程.

作用域线程 (Scoped threads)

let numbers = vec![1, 2, 3];

thread::scope(|s| {
    s.spawn(|| {
        println!("length: {}", numbers.len());
    });
    s.spawn(|| {
        for n in &numbers {
            println!("{n}");
        }
    });
});

使用作用域线程则无需转移外部变量的所有权, 因为所创建的线程都将自动 join.

评论