跳到主要内容

Rust线程

提示
  1. 创建和运行线程:Rust 使用 thread::spawn() 函数创建新线程,允许在程序中同时运行多个任务,但增加了复杂性。
  2. 使用 Join 句柄控制线程thread::spawn() 返回一个 JoinHandle,使用 join() 方法可以确保创建的线程完成任务后主线程才继续执行。
  3. 线程间的数据传递:使用 move 关键字可以将数据移动到线程中,确保数据安全。Rust 通过通道(mpsc::channel())支持线程间的消息传递,实现线程间的通信。

线程是进程的最小可执行单元。

线程允许我们将程序中的计算分割成多个线程。同时运行多个任务可以提高代码性能。然而,这也可能增加复杂性。

在 Rust 中创建新线程

在 Rust 中,我们可以使用 std 模块中的 thread::spawn() 函数创建一个原生操作系统线程。spawn 方法接受一个闭包作为参数。

这是 thread::spawn() 的语法,

thread::spawn(|| {
// 线程中要执行的代码
})

现在,让我们看一个例子。

use std::thread;
use std::time::Duration;

fn main() {
// 创建一个线程
thread::spawn(|| {
// 这里的一切都在一个独立的线程中运行
for i in 0..10 {
println!("{} 来自产生的线程!", i);
thread::sleep(Duration::from_millis(2));
}
});

// 主线程
for i in 0..5 {
println!("{} 来自主线程!", i);
thread::sleep(Duration::from_millis(1));
}
}

输出

0 来自主线程!
0 来自产生的线程!
1 来自主线程!
1 来自产生的线程!
2 来自主线程!
3 来自主线程!
2 来自产生的线程!
4 来自主线程!

在上述示例中,我们使用 thread::spawn() 函数创建了一个线程。该线程循环 0..5 并打印当前值。

同样地,我们在主线程中循环 0..5 并打印当前值。

我们还调用了 thread::sleep 来强制一个线程暂停执行一段时间,让另一个线程运行。

请注意,我们在产生的线程中休眠 2 毫秒,在主线程中休眠 1 毫秒。

这个程序的输出每次可能略有不同。重要的是要记住,如果主线程完成,无论其他线程是否完成运行,所有其他线程都会被关闭。

因此,尽管产生的线程应该打印到 i 为 9,但它只达到了 2,因为主线程关闭了。

Rust 中的 Join 句柄

产生的线程总是返回一个 join 句柄。如果我们希望产生的线程完成执行,我们可以将 thread::spawn 的返回值保存在一个变量中,然后在其上调用 join() 方法。

JoinHandlethread::spawn 的返回类型)上的 join() 方法等待产生的线程完成。

让我们看一个例子。

use std::thread;
use std::time::Duration;

fn main() {
// 创建一个线程并将句柄保存到变量中
let handle = thread::spawn(|| {
// 这里的一切都在一个独立的线程中运行
for i in 0..10 {
println!("{} 来自产生的线程!", i);
thread::sleep(Duration::from_millis(2));
}
});

// 主线程
for i in 0..5 {
println!("{} 来自主线程!", i);
thread::sleep(Duration::from_millis(1));
}

// 等待独立线程完成
handle.join().unwrap();
}

输出

0 来自主线程!
0 来自产生的线程!
1 来自主线程!
2 来自主线程!
1 来自产生的线程!
3 来自主线程!
2 来自产生的线程!
4 来自主线程!
3 来自产生的线程!
4 来自产生的线程!
5 来自产生的
在这里,我们保存了 `thread::spawn()` 函数的返回值,并将其绑定到一个名为 `handle` 的变量上。

在代码的最后一行,我们调用了 `handle` 的 `join()` 方法。在 `handle` 上调用 `join()` 会阻塞线程,直到该线程终止。

两个线程(主线程和生成的线程)会交替执行一段时间,但由于 `handle.join()`,主线程会等待,并且直到生成的线程结束后才会终止。

如果我们将 `handle.join()` 移到最后循环之前,输出将会改变,打印语句将不会交错。

```rust exec
use std::thread;
use std::time::Duration;

fn main() {
// 创建一个线程并将句柄保存到变量中
let handle = thread::spawn(|| {
// 这里的内容在一个单独的线程中运行
for i in 0..10 {
println!("{} 来自生成的线程!", i);
thread::sleep(Duration::from_millis(2));
}
});

// 等待单独的线程完成
handle.join().unwrap();

// 主线程
for i in 0..5 {
println!("{} 来自主线程!", i);
thread::sleep(Duration::from_millis(1));
}
}

输出

0 来自生成的线程!
1 来自生成的线程!
2 来自生成的线程!
3 来自生成的线程!
4 来自生成的线程!
5 来自生成的线程!
6 来自生成的线程!
7 来自生成的线程!
8 来自生成的线程!
9 来自生成的线程!
0 来自主线程!
1 来自主线程!
2 来自主线程!
3 来自主线程!
4 来自主线程!

因此,知道 join() 被调用的位置很重要。它决定了线程是否同时运行。

在 Rust 中使用带有线程的 move 闭包

可以通过将值作为参数传递给 thread::spawn() 函数,将值移动到一个单独的线程中。

让我们来看一个例子。

use std::thread;

fn main() {
// 主线程从这里开始
let message = String::from("Hello, World!");

// 将 message 值移动到一个单独的线程
let handle = thread::spawn(move || {
println!("{}", message);
});

// 等待线程结束
handle.join().unwrap();
}

输出

Hello, World!

这里,传递给 thread::spawn() 函数的闭包 || 使用 move 关键字表明它获取了 message 变量的所有权。

当一个值被移动到一个线程中时,该值的所有权被转移到该线程,主线程将无法再访问该值。

这意味着即使主线程已经完成,闭包仍然可以使用 message 变量。

让我们看看如果我们不在闭包前使用 move 关键字会发生什么。

use std::thread;

fn main() {
let message = String::from("Hello, World!");

// 不使用 move 而使用 message 变量
let handle = thread::spawn(|| {
println!("{}", message);
});

handle.join().unwrap();
}

输出

错误[E0373]: 闭包可能比当前函数活得更久,但它借用了 `message`,而 `message` 是由当前函数拥有的
--> src/main.rs:7:32
|
7 | let handle = thread::spawn(|| {
| ^^ 可能比借用的值 `message` 活得更久
8 | println!("{}", message);
| ------- 在这里借用了 `message`
|

此程序无法编译。在这里,Rust 将尝试将 message 变量借用给独立的线程。

7 |     let handle = thread::spawn(|| {
| ^^ 可能比借用的值 `message` 存活得更长

然而,Rust 不知道产生的线程将运行多长时间。因此,它无法确定对 message 变量的引用是否总是有效的。

通过在闭包前添加 move 关键字,我们强制闭包获取 message 变量或闭包内使用的任何变量的所有权。

我们告诉 Rust 主线程不会再使用 message 变量。这是 Rust 所有权的经典例子,展示了它如何帮助我们避免意外。要了解更多关于 Rust 中的所有权,请访问 Rust 所有权

请注意,将值移动到线程中对于并行性可能很有用,但如果不小心使用,也可能成为错误的源头。

在 Rust 中线程间发送消息

在 Rust 中,线程可以通过通道发送消息来相互通信。通道是一种在线程之间发送值的方式,可用于同步通信和避免数据竞争。

我们使用 std::sync::mpsc 模块中的 channel() 函数在 Rust 中创建通道。

让我们看看如何使用通道在线程之间通信。

use std::sync::mpsc;
use std::thread;

fn main() {
// 主线程从这里开始
// 创建一个新的通道
let (sender, receiver) = mpsc::channel();

// 产生一个新的线程
let handle = thread::spawn(move || {
// 从通道接收消息
let message = receiver.recv().unwrap();

println!("收到消息: {}", message);
});

let message = String::from("Hello, World!");
// 向通道发送消息
sender.send(message).unwrap();

// 等待产生的线程完成
handle.join().unwrap();
}

输出

收到消息: Hello, World!

这里,我们使用 channel() 函数创建了一个通道。std::sync::mpsc 模块提供了多生产者单消费者(mpsc)通道,可用于在线程之间发送值。

// 创建一个新的通道
let (sender, receiver) = mpsc::channel();

senderreceiver 变量代表通道的两个端点。发送端用于发送消息,而接收端用于接收消息。

// 产生一个新的线程
let handle = thread::spawn(move || {
// 从通道接收消息
let message = receiver.recv().unwrap();

println!("收到消息: {}", message);
});

我们还使用 thread::spawn() 函数创建了一个产生的线程。传递给函数的闭包使用 receiver.recv() 方法接收消息。

recv() 方法在通道上收到消息时阻塞,并返回一个 Result,表明是否收到了消息或发生了错误。

let message = String::from("Hello, World!");
// 向通道发送消息
sender.send(message).unwrap();

在主线程中,创建了一个 message 并使用 sender.send() 方法发送。send() 方法返回一个 Result,表明消息是否成功发送或发生了错误。

// 等待产生的线程完成
handle.join().unwrap();

最后,在句柄上调用 join() 方法,等待产生的线程完成后再退出程序。