07-线程基础
8.3.1 线程基础
如前所述,每个程序都以一个主线程开始启动。要从程序中的任意位置创建独立的执行点,主线程可以生成一个新线程,该线程将成为其子线程,子线程可以进一步产生自己的线程。让我们看看在Rust中如何以最简单的方式通过线程实现应用程序的并发:
// thread_basics.rs
use std::thread;
fn main() {
thread::spawn(|| {
println!("Thread!");
"Much concurrent, such wow!".to_string()
});
print!("Hello ");
}
在main函数中,我们会调用thread模块的spawn函数,该模块将一个无参数闭包作为参数。在这个闭包中,我们可以编写任何想要作为单独线程并发执行的代码。在闭包中,我们可以只是简单地输出一些文本并返回一个字符串。编译和运行此程序后,我们得到了以下输出结果:
$ rustc thread_basics.rs
$ ./thread_basics
Hello
很奇怪,我们看到“Hello”被输出,那么子线程中的“println!("Thread!");”语句到底发生了什么?对spawn的调用会创建线程并立即返回,线程开始并发执行而不会阻塞后面的指令。子线程是以分离状态创建的。在子线程有机会运行代码之前,程序抵达“print!("Hello");”语句时从main函数返回并退出。因此,子线程中的代码根本不会执行。为了允许子线程执行代码,我们需要等待子线程。为此,我们需要先将spawn返回的值分配给变量:
let child = thread::spawn(|| {
print!("Thread!");
String::from("Much concurrent, such wow!")
});
spawn函数会返回一个JoinHandle类型的值,我们将其存放在变量child中。这种类型是子线程的句柄,可用于连接线程——换句话说就是等待它的终止。如果我们忽略线程的JoinHandle类型,就没有办法等待线程。继续解析我们的代码,从main函数退出之前在子线程上调用join方法:
let value = child.join().expect("Failed joining child thread");
调用join会阻塞当前线程,并在执行join调用之后的任何代码行之前等待子线程完成。它返回一个Result值。由于我们知道这个线程没有发生灾难性故障,可以调用expect方法来获取 Result 中的字符串。如果一个线程正在连接自身或者遇到死锁,那么连接线程可能会失败。在这种情况下,它会返回一个Err变量,其值会传递给处理错误的panic!宏,不过这种情况下返回的值是Any类型,必须将其转换为适当的类型。我们更新后的代码如下所示:
// thread_basics_join.rs
use std::thread;
fn main() {
let child = thread::spawn(|| {
println!("Thread!");
String::from("Much concurrent, such wow!")
});
print!("Hello ");
let value = child.join().expect("Failed joining child thread");
println!("{}", value);
}
这是程序的输出结果:
$ ./thread_basics_join
Hello Thread!
Much concurrent, such wow!
我们编写了第一个并发的hello world程序。接下来让我们来探索thread模块的其他API。