Rust 学习笔记(31)-创建线程

参考章节《Rust 程序设计语言》第16.1章 使用线程同时运行代码

线程就是程序内部,可以同时运行的独立部分。平常我们说的多线程编程,就是指编写内部有多个任务同时运行的程序

使用 spawn 创建新线程

为了创建一个新线程,需要调用 thread::spawn 函数并传递一个闭包,并在其中包含希望在新线程运行的代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::thread;
use std::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));
    }
}

运行这个程序

1
2
3
4
5
6
7
8
9
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 spawned thread!

你会发现程序只打印到 5 就结束了,那是因为当主线程结束时,它的子线程也会结束,而不管其是否执行完毕

使用 join 等待所有线程结束

通过将 thread::spawn 方法的返回值保存在变量中,然后对其调用 join 方法,当对其调用 join 方法时,它会等待其线程结束

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
use std::thread;
use std::time::Duration;

fn main() {
    // 将 thread::spawn 的返回值保存在变量 handle 中,thread::spawn 的返回值类型是 JoinHandle。JoinHandle 是一个拥有所有权的值,当对其调用 join 方法时,它会等待其线程结束。
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    //handle.join().unwrap(); // 如果把下面的join提到这里,则会先等待子线程运行完毕后,再执行下面的for循环

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

    handle.join().unwrap(); // 就算主线程运行完毕后,在这里也会等待子线程运行完毕,而不是直接退出
}

线程与 move 闭包

这段代码并不能编译,为什么?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use std::thread;

fn main() {
    let v = vec![1, 2, 3];

    let handle = thread::spawn(|| {
        println!("Here's a vector: {:?}", v);
    });

    // drop(v) // 想象一下,如果这里v被释放了

    handle.join().unwrap();
}

答:由于闭包会捕获环境,Rust 会推断如何捕获 v,因为 println! 只需要 v 的引用,所以闭包尝试借用 v
但这里的问题是Rust 并不知道线程会在什么时候执行,如果在线程执行之前v 被释放了,那么程序肯定就会出错,所以Rust不允许你这么做

线程的执行是根据CPU的调度来的,在单核CPU上,线程会去和其他线程争抢CPU执行权谁抢到就执行谁,没抢到的则转入后台等待下一次争夺CPU执行权
而在多核CPU上,线程就可以同时运行,但就算同时运行,v有可能被其他线程释放掉正是由于存在这种不确定因素,所以Rust出于安全考虑不允许你编译这段代码

所以说这么多怎么解决这个问题呢?

很简单,我们看如下代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use std::thread;

fn main() {
    let v = vec![1, 2, 3];

    let handle = thread::spawn(move || {
        println!("Here's a vector: {:?}", v);
    });

    handle.join().unwrap();
}

通过在闭包之前增加 move 关键字,我们强制闭包获取其使用的值的所有权,而不是任由 Rust 推断它应该借用值。

总结一下

  1. 可以通过thread::spawn并传递一个闭包来开启一个线程
  2. join方法可以等待其线程运行结束,需要特别注意的是join等待线程结束,而不是join时才启动线程。
updatedupdated2025-03-012025-03-01