Rust如何使用线程同时运行代码

 更新时间:2025年02月26日 08:55:02   作者:Hello.Reader  
Rust使用1:1线程模型,通过std::thread::spawn创建线程,返回JoinHandle用于等待线程完成,闭包默认借用外部变量,使用move关键字转移所有权,多线程共享数据时需使用并发原语,如Mutex、RwLock、Arc等,以避免竞态条件

一、Rust 的线程模型

Rust 标准库使用的是 1:1 的线程模型,即每一个语言层的线程都对应一个操作系统线程。Rust 中通过标准库提供的 std::thread 模块来创建、管理线程。

当然,也有一些第三方库会采用不同的线程模型,或者利用异步(async)机制来实现并发(比如 Rust 的 async/await 机制),在面对具体需求时可以根据实际情况做选择。

二、创建线程:thread::spawn

要在 Rust 中创建一个新线程,可以使用 thread::spawn 函数,并向它传递一个闭包(closure)。闭包中包含需要在线程中执行的代码。

例如:

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

fn main() {
    // 使用 thread::spawn 创建新的线程
    thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    // 主线程也执行一些操作
    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}

在上面的例子里,我们在子线程中打印数字,同时在主线程中也打印数字。由于操作系统会对线程进行调度,输出的顺序无法完全预测。可能主线程先打印,也可能子线程先打印,或者两者交错执行。

需要注意的是,当主线程结束时,所有通过 spawn 创建的子线程会被强制终止,即使子线程还没有执行完。

三、等待线程完成:JoinHandle 与 join

如果希望确保子线程的代码一定会执行完,那么就需要在主线程结束前等待子线程。thread::spawn 的返回值是一个 JoinHandle,可以用它来调用 join 方法,阻塞(block)当前线程,直到对应的子线程执行完成。

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

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

    // 如果先做主线程自己的工作,再等待子线程
    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }

    // 调用 join,阻塞主线程,直到子线程结束
    handle.join().unwrap();
}

当我们在主线程中调用 handle.join() 时,主线程会暂停执行,直到子线程完成工作。这样就能确保在程序退出前,所有线程都能顺利完成执行。

如果把 join 放在主线程的循环之前,那么主线程会先等待子线程结束,才会进行自身的打印操作——这样就不会再看到主线程与子线程的输出交错了。

四、move 闭包与线程

多线程编程中常常需要在线程间传递数据或访问主线程中的变量。

在 Rust 中,如果一个闭包想要捕获外部变量,就要考虑该变量的所有权或引用生命周期问题。

4.1.问题场景

如下示例所示,如果我们在主线程中创建一个向量 v,然后在子线程中直接打印这个向量,就会出错:

use std::thread;

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

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

    // ...
    handle.join().unwrap();
}

编译时,Rust 会提示闭包捕获的是对 v 的引用,但无法保证在子线程运行时 v 依旧有效:主线程可能在子线程使用 v 之前就结束了,让 v 不再有效,从而导致潜在的悬垂引用(dangling reference)。

4.2.使用 move 关键字

为了解决这个问题,需要在闭包前面加上 move 关键字,这样可以把闭包中用到的外部数据移动到闭包的所有权中。

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 会把 v 的所有权从主线程转移到子线程,从而保证子线程在使用 v 时不会遇到生命周期问题。不过需要注意的是,这样一来,主线程就不能再使用 v 了,因为所有权已经被移动出去了。

4.3.不能与 drop 共用所有权

如果尝试同时在主线程中显式调用 drop(v) 并且在子线程中使用 v,无论有没有用 move,都行不通。Rust 的所有权规则会保证同一份数据不会被多次释放或引用到失效的数据。所以,在设计多线程逻辑时,需要明确划分数据的所有权与生命周期,以避免死锁、竞态条件或悬垂引用等问题。

五、小结

  1. Rust 标准库中的线程模型:Rust 使用一对一(1:1)模型,每个语言线程对应一个系统线程。
  2. 创建线程:使用 thread::spawn 来创建子线程,传入一个闭包作为要执行的代码。
  3. 线程同步:通过返回的 JoinHandle 调用 join,可以阻塞主线程并等待子线程完成执行。
  4. 所有权与生命周期:使用 move 关键字将闭包所需的变量从主线程移动到子线程,从而避免引用冲突或无效引用。
  5. 小心共享数据:当多个线程需要同时访问或修改同一份数据时,需要使用安全的并发原语(例如 MutexRwLockArc 等),否则会出现竞态条件。

在 Rust 中编写并发程序时,我们需要充分利用所有权与借用检查器提供的安全保障,同时对多线程逻辑进行精心设计。尽管多线程编程能带来性能上的提升,但也应关注潜在的风险,并通过 Rust 的工具链和语言特性来尽量减少错误,写出更安全、更可靠的并发应用。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • rust中trait的使用方法详解

    rust中trait的使用方法详解

    trait用中文来讲就是特征,它就是一个标记,只不过这个标记被用在特定的地方,也就是类型参数的后面,下面我们就来学习一下trait的具体使用方法吧
    2023-12-12
  • 详解Rust中三种循环(loop,while,for)的使用

    详解Rust中三种循环(loop,while,for)的使用

    我们常常需要重复执行同一段代码,针对这种场景,Rust 提供了多种循环(loop)工具。一个循环会执行循环体中的代码直到结尾,并紧接着回到开头继续执行。而 Rust 提供了 3 种循环:loop、while 和 for,下面逐一讲解
    2022-09-09
  • Rust anyhow 简明示例教程

    Rust anyhow 简明示例教程

    anyhow 是 Rust 中的一个库,旨在提供灵活的、具体的错误处理能力,建立在 std::error::Error 基础上,主要用于那些需要简单错误处理的应用程序和原型开发中,本文给大家分享Rust anyhow 简明教程,一起看看吧
    2024-06-06
  • 利用Rust编写一个简单的字符串时钟

    利用Rust编写一个简单的字符串时钟

    这篇文章主要为大家详细介绍了一个用rust写的一个简单的练手的demo,一个字符串时钟,在终端用字符串方式显示当前时间,感兴趣的小伙伴可以了解一下
    2022-12-12
  • Rust多线程Web服务器搭建过程

    Rust多线程Web服务器搭建过程

    这篇文章主要介绍了Rust多线程 Web 服务器搭建过程,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-04-04
  • Rust使用libloader调用动态链接库

    Rust使用libloader调用动态链接库

    这篇文章主要为大家介绍了Rust使用libloader调用动态链接库示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • C++的替代:微软如何使用rust?

    C++的替代:微软如何使用rust?

    这篇文章主要介绍了微软如何使用rust的,帮助大家了解c++和rust这两门编程语言的联系与区别,感兴趣的朋友可以了解下
    2020-09-09
  • rust异步编程详细讲解

    rust异步编程详细讲解

    这篇文章主要介绍了rust异步编程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-12-12
  • Rust动态调用字符串定义的Rhai函数方式

    Rust动态调用字符串定义的Rhai函数方式

    Rust中使用Rhai动态调用字符串定义的函数,通过eval_expression_with_scope实现,但参数传递和函数名处理有局限性,使用FnCall功能更健壮,但更复杂,总结提供了更通用的方法,但需要处理更多错误情况
    2025-02-02
  • rust zip异步压缩与解压的代码详解

    rust zip异步压缩与解压的代码详解

    在使用actix-web框架的时候,如果使用zip解压任务将会占用一个工作线程,因为zip库是同步阻塞的,想用异步非阻塞需要用另一个库,下面介绍下rust zip异步压缩与解压的示例,感兴趣的朋友一起看看吧
    2024-04-04

最新评论