66、Threads(线程)
Threads(线程)
在开始编写多线程代码之前,让我们退一步先学习一下什么是线程,以及为什么要使用线程。
什么是线程?
线程,是由底层操作系统管理的可执行上下文。每一个线程都有自己的堆栈和指令指针。
一个process
(进程)可以管理多个thread
(线程)。这些线程共享相同的内存空间,这意味着它们可以访问相同的数据。
线程是一种逻辑结构。归根结底,我们一次只能在一个CPU核心上执行一组指令。
由于线程数可能比CPU内核多得多,因此操作系统的调度程序负责决定在任何特定时间运行哪个线程,并将CPU时间分配给这些线程,以最大限度地提高吞吐量和响应速度。
main
当Rust程序启动时,它会在单线程(即 main thread 主线程)上运行。该线程由操作系统创建,负责运行main
函数。
use std::thread;
use std::time::Duration;
fn main() {
loop {
thread::sleep(Duration::from_secs(2));
println!("Hello from the main thread!");
}
}
std::thread
Rust的标准库提供了一个模块,std::thread
,允许我们创建和管理线程。
spawn
我们可以使用std::thread::spawn
创建新线程,并在其上执行代码。
举个例子:
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
loop {
thread::sleep(Duration::from_secs(1));
println!("Hello from a thread!");
}
});
loop {
thread::sleep(Duration::from_secs(2));
println!("Hello from the main thread!");
}
}
如果我们在Rust playground上运行这个程序,我们可以看到主线程和生成的线程同时运行。
每个线程都独立于其他的线程工作。
Process termination(进程终止)
当主线程结束时,整个进程都将退出。
一个被创建的子线程会继续运行,直到它自身完成,或者主线程结束为止。
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
loop {
thread::sleep(Duration::from_secs(1));
println!("Hello from a thread!");
}
});
thread::sleep(Duration::from_secs(5));
}
在上面的示例中,我们可以看到“Hello from a thread!”信息大约被打印了五次。
实际大概率是四次。
然后,主线程将结束(当main中的sleep
执行完毕并调用return时),生成的线程也将终止,因为整个进程已经退出。
join
我们也可以通过调用spawn
返回的JoinHandle
上的join
方法来等待生成的线程结束。
use std::thread;
fn main() {
let handle = thread::spawn(|| {
println!("Hello from a thread!");
});
handle.join().unwrap();
}
在这个例子中,主线程将等待生成的线程结束后再退出。
这在两个进程中引入了一种同步形式:在程序退出之前,我们肯定能看到“Hello from a thread!”信息被打印出来,因为主线程在生成的线程完成任务之前不会退出。
练习
这一节的练习当然就是让我们开发多线程程序了。题目是这样的:
// TODO: implement a multi-threaded version of the `sum` function
// using `spawn` and `join`.
// Given a vector of integers, split the vector into two halves and
// sum each half in a separate thread.
// Caveat: We can't test *how* the function is implemented,
// we can only verify that it produces the correct result.
// You _could_ pass this test by just returning `v.iter().sum()`,
// but that would defeat the purpose of the exercise.
//
// Hint: you won't be able to get the spawned threads to _borrow_
// slices of the vector directly. You'll need to allocate new
// vectors for each half of the original vector. We'll see why
// this is necessary in the next exercise.
use std::thread;
// TODO: 实现一个支持多线程的 sum 函数
// 使用 spawn 和 join。
// 给定一个整数向量,将其拆分为两半,并在两个独立的线程中分别对每 half 求和。
// 注意:我们无法测试函数的具体实现方式,
// 只能验证其结果是否正确。
// 你可以通过直接返回 v.iter().sum() 来通过测试,
// 但这违背了本练习的设计初衷。
// 提示:你无法让生成的线程借用原向量的子切片。
// 你需要为原向量的每一半分配新的向量。我们将在下一个练习中了解原因。
跟着IDE的提示,我写的代码是这个:
pub fn sum(v: Vec<i32>) -> i32 {
let handle = thread::spawn(move || {
let mut sum1=0;
for i in 0..v.len()/2 {
sum1 += v[i];
}
sum1
});
let handle2 = thread::spawn(move || {
let mut sum2=0;
for i in v.len()/2..v.len() {
sum2 += v[i];
}
sum2
});
handle.join().unwrap()+handle2.join().unwrap()
}
但这是不对的,看一下官方的代码:
pub fn sum(v: Vec<i32>) -> i32 {
let mid = v.len() / 2;
let (v1, v2) = v.split_at(mid);
let v1 = v1.to_vec();
let v2 = v2.to_vec();
let handle1 = thread::spawn(move || v1.into_iter().sum::<i32>());
let handle2 = thread::spawn(move || v2.into_iter().sum::<i32>());
handle1.join().unwrap() + handle2.join().unwrap()
}
看来问题就在于两个线程同时访问了一个字段,假如我提前把Vec
拷贝两份呢?
果然顺利通过了,估计这个就是原题提示里面的问题:我们不能随意地让多个线程共享资源。
这一节的学习就先到这里了。