线程是进程中最小的可执行单元。
线程允许我们将程序中的计算分成多个线程。同时运行多个任务可以提高代码的性能。然而,它也会增加复杂性。
在 Rust 中创建新线程
在 Rust 中,我们可以使用std
模块中的thread::spawn()
函数创建一个本地操作系统线程。spawn
方法接受一个闭包作为参数。
这是thread::spawn()
的语法,
thread::spawn(|| {
// code to execute in the thread
})
现在,让我们看一个示例。
use std::thread;
use std::time::Duration;
fn main() {
// create a thread
thread::spawn(|| {
// everything in here runs in a separate thread
for i in 0..10 {
println!("{} from the spawned thread!", i);
thread::sleep(Duration::from_millis(2));
}
});
// main thread
for i in 0..5 {
println!("{} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
}
输出
0 from the main thread! 0 from the spawned thread! 1 from the main thread! 1 from the spawned thread! 2 from the main thread! 3 from the main thread! 2 from the spawned thread! 4 from the main thread!
在上面的示例中,我们使用thread::spawn()
函数创建了一个线程。该线程循环遍历0..5
并打印当前值。
同样,我们有一个主线程,其中循环遍历0..5
并打印当前值。
我们还调用thread::sleep
来强制线程暂停执行一小段时间,以便另一个线程运行。
请注意,我们在创建的线程中休眠2毫秒,在主线程中休眠1毫秒。
每次运行此程序的输出可能会略有不同。这里要记住的重要一点是,如果主线程完成,所有其他线程都会被关闭,无论它们是否已完成运行。
因此,即使创建的线程应该一直打印到i
为9,但由于主线程关闭,它只运行到2。
Rust 中的 Join Handles
创建的线程始终返回一个 join handle。如果我们希望创建的线程完成执行,我们可以将thread::spawn
的返回值保存在一个变量中,然后在其上调用join()
方法。
thread::spawn
的返回类型JoinHandle
上的join()
方法会等待创建的线程完成。
让我们看一个例子。
use std::thread;
use std::time::Duration;
fn main() {
// create a thread and save the handle to a variable
let handle = thread::spawn(|| {
// everything in here runs in a separate thread
for i in 0..10 {
println!("{} from the spawned thread!", i);
thread::sleep(Duration::from_millis(2));
}
});
// main thread
for i in 0..5 {
println!("{} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
// wait for the separate thread to complete
handle.join().unwrap();
}
输出
0 from the main thread! 0 from the spawned thread! 1 from the main thread! 2 from the main thread! 1 from the spawned thread! 3 from the main thread! 2 from the spawned thread! 4 from the main thread! 3 from the spawned thread! 4 from the spawned thread! 5 from the spawned thread! 6 from the spawned thread! 7 from the spawned thread! 8 from the spawned thread! 9 from the spawned thread!
在这里,我们保存了thread::spawn()
函数的返回值,并将其绑定到名为handle
的变量。
在代码的最后一行,我们调用handle
的join()
方法。调用handle
上的join()
会阻塞线程,直到线程终止。
两个线程(主线程和创建的线程)会持续一段时间交替运行,但主线程由于handle.join()
而等待,直到创建的线程完成才结束。
如果我们把handle.join()
移到最后一个循环之前,输出将会改变,打印语句也不会交错。
use std::thread;
use std::time::Duration;
fn main() {
// create a thread and save the handle to a variable
let handle = thread::spawn(|| {
// everything in here runs in a separate thread
for i in 0..10 {
println!("{} from the spawned thread!", i);
thread::sleep(Duration::from_millis(2));
}
});
// wait for the separate thread to complete
handle.join().unwrap();
// main thread
for i in 0..5 {
println!("{} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
}
输出
0 from the spawned thread! 1 from the spawned thread! 2 from the spawned thread! 3 from the spawned thread! 4 from the spawned thread! 5 from the spawned thread! 6 from the spawned thread! 7 from the spawned thread! 8 from the spawned thread! 9 from the spawned thread! 0 from the main thread! 1 from the main thread! 2 from the main thread! 3 from the main thread! 4 from the main thread!
因此,知道join()
在哪里被调用很重要。这将决定线程是否同时运行。
在 Rust 中使用 move 闭包与线程
可以通过将一个值作为参数传递给thread::spawn()
函数来将其移动到单独的线程中。
让我们看一个例子。
use std::thread;
fn main() {
// main thread starts here
let message = String::from("Hello, World!");
// move the message value to a separate thread
let handle = thread::spawn(move || {
println!("{}", message);
});
// wait for the thread to finish
handle.join().unwrap();
}
输出
Hello, World!
在这里,传递给thread::spawn()
函数的闭包||
使用move
关键字来指示它获取message
变量的所有权。
当一个值被移动到线程中时,该值的所有权就转移给了该线程,主线程将无法再访问该值。
这意味着即使主线程完成,闭包也可以使用message
变量。
让我们看看如果我们不在闭包前面使用move
关键字会发生什么。
use std::thread;
fn main() {
let message = String::from("Hello, World!");
// using the message variable without a move
let handle = thread::spawn(|| {
println!("{}", message);
});
handle.join().unwrap();
}
输出
error[E0373]: closure may outlive the current function, but it borrows `message`, which is owned by the current function --> src/main.rs:7:32 | 7 | let handle = thread::spawn(|| { | ^^ may outlive borrowed value `message` 8 | println!("{}", message); | ------- `message` is borrowed here |
在这种情况下,程序将无法编译。在这里,Rust 会尝试将message
变量借用给单独的线程。
7 | let handle = thread::spawn(|| {
| ^^ may outlive borrowed value `message`
然而,Rust 不知道创建的线程会运行多久。因此,它无法判断对message
变量的引用是否始终有效。
通过在闭包前面添加move
关键字,我们强制闭包获取message
变量或闭包内使用的任何变量的所有权。
我们正在告诉 Rust 主线程不再使用message
变量。这是 Rust 所有权的一个典型示例,它如何使我们免于发生意外。要了解更多关于 Rust 所有权的信息,请访问Rust 所有权。
请注意,将值移动到线程中对于并行处理可能很有用,但如果使用不当,也可能成为错误的来源。
在 Rust 中通过线程发送消息
在 Rust 中,线程可以通过通道发送消息来相互通信。通道是一种在线程之间发送值的方式,它可以用于同步通信和避免数据竞争。
我们使用std::sync::mspsc
模块中的channel()
函数在 Rust 中创建一个通道。
让我们看看如何使用通道在线程之间进行通信。
use std::sync::mpsc;
use std::thread;
fn main() {
// main thread starts here
// create a new channel
let (sender, receiver) = mpsc::channel();
// spawn a new thread
let handle = thread::spawn(move || {
// receive message from channel
let message = receiver.recv().unwrap();
println!("Received message: {}", message);
});
let message = String::from("Hello, World!");
// send message to channel
sender.send(message).unwrap();
// wait for spawned thread to finish
handle.join().unwrap();
}
输出
Received message: Hello, World!
在这里,我们使用channel()
函数创建一个通道。std::sync::mpsc
模块提供了多生产者、单消费者(mspc)通道,可用于在线程之间发送值。
// create a new channel
let (sender, receiver) = mpsc::channel();
sender
和receiver
变量代表通道的两个端点。发送者端点用于发送消息,而接收者端点用于接收消息。
// spawn a new thread
let handle = thread::spawn(move || {
// receive message from channel
let message = receiver.recv().unwrap();
println!("Received message: {}", message);
});
我们还使用thread::spawn()
函数创建一个创建的线程。传递给函数的闭包使用receiver.recv()
方法接收消息。
recv()
方法会阻塞,直到通道上收到消息,它会返回一个Result
,指示是否收到了消息或发生了错误。
let message = String::from("Hello, World!");
// send message to channel
sender.send(message).unwrap();
在主线程中,创建了一个message
并通过sender.send()
方法发送。send()
方法返回一个Result
,指示消息是否成功发送或发生了错误。
// wait for spawned thread to finish
handle.join().unwrap();
最后,在 handle 上调用join()
方法,以等待创建的线程在程序退出前完成。