Rust 学习笔记:Thread

Rust 学习笔记:Thread

在大多数当前的操作系统中,已执行的程序代码在一个进程中运行,操作系统将同时管理多个进程。在程序中,也可以有同时运行的独立部分。运行这些独立部件的功能称为线程。

将程序中的计算拆分为多个线程以同时运行多个任务可以提高性能,但也增加了复杂性。因为线程可以同时运行,所以不能保证不同线程上代码部分的运行顺序。这可能会导致一些问题,比如:

  • 竞争条件,线程以不一致的顺序访问数据或资源
  • 死锁,两个线程相互等待
  • 只在某些情况下发生的 bug,难以可靠地重现和修复

Rust 标准库使用 1:1 的线程实现模型,即程序中的一个语言线程使用一个操作系统线程。

用 spawn 创建一个新线程

要创建一个新线程,我们调用 thread::spawn 函数并传递给它一个闭包,其中包含我们想要在新线程中运行的代码。

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

fn main() {
    thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {i} from the spawned thread!");
            thread::sleep(Duration::from_millis(1));
        }
    });

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

程序输出:

在这里插入图片描述

对 thread::sleep 的调用强制线程在短时间内停止执行,允许另一个线程运行。

请注意,当 Rust 程序的主线程完成时,所有派生线程都被关闭,无论它们是否已经完成运行。尽管我们告诉派生线程打印到 i = 9,但在主线程关闭之前,它只打印到 5。

使用 join 句柄等待所有线程完成

通过将 thread::spawn 的返回值保存在一个变量中,我们可以解决派生线程不运行或过早结束的问题。

thread::spawn 的返回类型是 JoinHandle<T>,是一个句柄。在句柄上调用 join 会阻塞当前运行的线程,直到该句柄所表示的线程终止为止。

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

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {i} from the spawned thread!");
            thread::sleep(Duration::from_millis(1));
        }
    });

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

    handle.join().unwrap();
}

程序输出:

在这里插入图片描述

主线程和 handle 线程交替进行,但是主线程因为调用 handle.join() 而等待,直到派生线程完成才结束。

让我们看看当我们把 handle.join() 移到 main 中的 for 循环之前会发生什么,就像这样:

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

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {i} from the spawned thread!");
            thread::sleep(Duration::from_millis(1));
        }
    });

    handle.join().unwrap();

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

程序输出:

在这里插入图片描述

主线程将等待派生线程完成,然后运行它的 for 循环,这样输出就不会再交错了。

通过线程使用 move 闭包

我们经常将 move 关键字与传递给 thread::spawn 的闭包一起使用,因为闭包将从环境中获取它使用的值的所有权,从而将这些值的所有权从一个线程转移到另一个线程。

在派生线程中使用来自主线程的数据,派生线程的闭包必须捕获它所需的值。下列代码展示了在主线程中创建一个 vector 并在派生线程中使用它的尝试。

use std::thread;

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

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

    handle.join().unwrap();
}

闭包使用 v,因此它将捕获 v 并使其成为闭包环境的一部分。因为 thread::spawn 在一个新线程中运行这个闭包,我们应该能够在这个新线程中访问 v。

然而,运行报错了:

在这里插入图片描述

Rust 无法判断派生线程将运行多长时间,因此它不知道对 v 的引用是否总是有效的。例如,我们可能在 join() 前就销毁了 v。

通过在闭包之前添加 move 关键字,我们强制闭包获得它所使用的值的所有权,而不是让 Rust 推断它应该借用这些值。

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 将 v 的所有权转移到派生线程,我们向 Rust 保证主线程不再使用 v。如果我们还试图在主线程中使用 v,就违反了所有权规则,得到编译错误:use of moved value: `v`。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值