Rust

2025年08月05日更新 7 人订阅
原价: ¥ 6 限时优惠
专栏简介 Rust编程语言之错误处理 Rust 语言之 flod Rust编程语言之Cargo、Crates.io详解 Rust编程语言之枚举与模式匹配 Rust语言 - 接口设计的建议之受约束(Constrained) Rust编程语言之无畏并发 Rust语言 - 接口设计的建议之灵活(flexible) Rust语言 - 接口设计的建议之显而易见(Obvious) Rust语言 - 接口设计的建议之不意外(unsurprising) Rust 实战:构建实用的 CLI 工具 HTTPie Rust编程语言学习之高级特性 Rust内存管理揭秘:深度剖析指针与智能指针 解决Rust中数组和切片的编译时大小问题 《Rust编程之道》学习笔记一 Rust Async 异步编程 简易教程 使用 Async Rust 构建简单的 P2P 节点 Rust编程语言入门之模式匹配 Rust async 编程 Rust编程语言之编写自动化测试 Rust编程语言之函数式语言特性:迭代器和闭包 《Rust编程之道》学习笔记二 Rust Tips 比较数值 使用 Rust 开发一个微型游戏 Rust编程初探:深入理解Struct结构体 深入理解Rust中的内存管理:栈、堆与静态内存详解 深入理解 Rust 结构体:经典结构体、元组结构体和单元结构体的实现 深入掌握 Rust 结构体:从模板到实例化的完整指南 深入理解Rust中的结构体:逻辑与数据结合的实战示例 深入理解 Rust 枚举:从基础到实践 掌握Rust字符串的精髓:String与&str的最佳实践 全面解析 Rust 模块系统:实战案例与应用技巧 Rust 中的 HashMap 实战指南:理解与优化技巧 掌握Rust模式匹配:从基础语法到实际应用 Rust 中的面向对象编程:特性与实现指南 深入理解 Rust 的 Pin 和 Unpin:理论与实践解析 Rust Trait 与 Go Interface:从设计到实战的深度对比 从零开始:用 Rust 和 Axum 打造高效 Web 应用 Rust 错误处理详解:掌握 anyhow、thiserror 和 snafu Rust 如何优雅实现冒泡排序 链表倒数 K 节点怎么删?Python/Go/Rust 实战 用 Rust 玩转数据存储:JSON 文件持久化实战 Rust实战:打造高效字符串分割函数 如何高效学习一门技术:从知到行的飞轮效应 Rust 编程入门:Struct 让代码更优雅 Rust 编程:零基础入门高性能开发 用 Rust 写个猜数游戏,编程小白也能上手! Rust 入门教程:变量到数据类型,轻松掌握! 深入浅出 Rust:函数、控制流与所有权核心特性解析 从零开始:用 Rust 和 Axum 打造高效 Web 服务 Rust 集合类型解析:Vector、String、HashMap 深入浅出Rust:泛型、Trait与生命周期的硬核指南 Rust实战:博物馆门票限流系统设计与实现 用 Rust 打造高性能图片处理服务器:从零开始实现类似 Thumbor 的功能 Rust 编程入门实战:从零开始抓取网页并转换为 Markdown 深入浅出 Rust:高效处理二进制数据的 Bytes 与 BytesMut 实战 Rust智能指针:解锁内存管理的进阶之道 用 Rust 打造命令行利器:从零到一实现 mini-grep 解锁Rust代码组织:轻松掌握Package、Crate与Module Rust 所有权:从内存管理到生产力释放 深入解析 Rust 的面向对象编程:特性、实现与设计模式 Rust + Protobuf:从零打造高效键值存储项目 bacon 点燃 Rust:比 cargo-watch 更爽的开发体验 用 Rust 打造微型游戏:从零开始的 Flappy Dragon 开发之旅 函数式编程的Rust之旅:闭包与迭代器的深入解析与实践 探索Rust编程之道:从设计哲学到内存安全的学习笔记 精读《Rust编程之道》:吃透语言精要,彻底搞懂所有权与借用 Rust 避坑指南:搞定数值比较,别再让 0.1 + 0.2 != 0.3 困扰你! 告别 Vec!掌握 Rust bytes 库,解锁零拷贝的真正威力 告别竞态条件:基于 Axum 和 Serde 的 Rust 并发状态管理最佳实践 Rust 异步编程实践:从 Tokio 基础到阻塞任务处理模式 Rust 网络编程实战:用 Tokio 手写一个迷你 TCP 反向代理 (minginx) 保姆级教程:Zsh + Oh My Zsh 终极配置,让你的 Ubuntu 终端效率倍增 不止于后端:Rust 在 Web 开发中的崛起之路 (2024数据解读) Rust核心利器:枚举(Enum)与模式匹配(Match),告别空指针,写出优雅健壮的代码 Rust 错误处理终极指南:从 panic! 到 Result 的优雅之道 想用 Rust 开发游戏?这份超详细的入门教程请收好! 用 Rust 实现 HTTPie:一个现代 CLI 工具的构建过程 Rust 异步实战:从0到1,用 Tokio 打造一个高性能并发聊天室 深入 Rust 核心:彻底搞懂指针、引用与智能指针 Rust 生产级后端实战:用 Axum + sqlx 打造高性能短链接服务 深入 Rust 内存模型:栈、堆、所有权与底层原理 Rust 核心概念解析:引用、借用与内部可变性 掌握 Rust 核心:生命周期与借用检查全解析 Rust 内存布局深度解析:从对齐、填充到 repr 属性 Rust Trait 分派机制:静态与动态的抉择与权衡 Rust Thread::Builder 用法详解:线程命名与栈大小设置 Rust 泛型 Trait:关联类型与泛型参数的核心区别 Rust Scoped Threads 实战:更安全、更简洁的并发编程 Rust 核心设计:孤儿规则与代码一致性解析

Rust Scoped Threads 实战:更安全、更简洁的并发编程

RustScopedThreads实战:更安全、更简洁的并发编程在Rust中进行并发编程时,管理线程的生命周期和数据共享一直是一个核心挑战。传统的std::thread::spawn要求线程闭包拥有'static生命周期,这使得直接从父线程借用数据变得复杂,通常需要Arc等工

Rust Scoped Threads 实战:更安全、更简洁的并发编程

在 Rust 中进行并发编程时,管理线程的生命周期和数据共享一直是一个核心挑战。传统的 std::thread::spawn 要求线程闭包拥有 'static 生命周期,这使得直接从父线程借用数据变得复杂,通常需要 Arc 等工具。为了解决这一痛点,Rust 引入了带作用域的线程(Scoped Threads),提供了一种更安全、更符合人体工程学的方式来处理并发任务。本文将深入探讨 Scoped Threads 的核心概念、优势与局限,并通过多个实例,展示如何利用它编写出更简洁、更安全的并发代码。

作用域线程 Scoped Threads

Rust Scoped Threads 带作用域的线程

什么是限定作用域线程?

  • 定义:使用 std::thread::scope 创建的线程,生命周期受限于特定作用域
  • 特性:线程在作用域结束前必须终止,无需手动管理 JoinHandle

主要优点

简化线程管理:

  • 无需手动调用 join(),作用域自动确保线程退出。
  • 减少管理线程生命周期的复杂性。

安全的数据访问:

  • 编译器保证数据在作用域内有效,限制所有权的可能性。

简化工作流:

  • 闭包可直接访问本地变量,编写线程函数更直观。
  • 提高代码可读性和维护性

局限性

线程生命周期受限:

  • 你不能在一个作用域中创建一个线程并期望它永远运行。

强制终止:

  • 父作用域在继续执行前,会强制等待所有子线程终止。

实操

创建项目

cargo new scoped_threads
    Creating binary (application) `scoped_threads` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

切换到项目目录并用 cursor 打开项目

cd scoped_threads
cc # open -a cursor .

🚩 示例一 & 示例二:起点:手动管理

示例一:

use std::{thread, time::Duration};

fn main() {
    let mut handles = vec![];

    for i in 0..5 {
        let handle = thread::spawn(move || {
            thread::sleep(Duration::from_secs(1));
            println!("Normal thread: {i}");
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

构建并运行示例一

RustJourney/scoped_threads on  main [?] is 📦 0.1.0 via 🦀 1.88.0 on 🐳 v28.2.2 (orbstack) 
➜ cargo build             
   Compiling scoped_threads v0.1.0 (/Users/qiaopengjun/Code/Rust/RustJourney/scoped_threads)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.55s

RustJourney/scoped_threads on  main [?] is 📦 0.1.0 via 🦀 1.88.0 on 🐳 v28.2.2 (orbstack) 
➜ cargo run  
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/scoped_threads`
Normal thread: 0
Normal thread: 1
Normal thread: 3
Normal thread: 4
Normal thread: 2

示例二:

use std::{thread, time::Duration};

fn main() {
    let mut handles = vec![];

    for i in 0..5 {
        let handle = thread::spawn(move || {
            thread::sleep(Duration::from_secs(1));
            println!("Normal thread: {i}");
        });
        handles.push(handle);
    }

    // for handle in handles {
    //     handle.join().unwrap();
    // }

    handles
        .into_iter()
        .for_each(|handle| handle.join().unwrap());
}

运行示例二

RustJourney/scoped_threads on  main [?] is 📦 0.1.0 via 🦀 1.88.0 on 🐳 v28.2.2 (orbstack) 
➜ cargo run
   Compiling scoped_threads v0.1.0 (/Users/qiaopengjun/Code/Rust/RustJourney/scoped_threads)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.56s
     Running `target/debug/scoped_threads`
Normal thread: 2
Normal thread: 3
Normal thread: 0
Normal thread: 4
Normal thread: 1

示例一 & 示例二: 这两段代码展示了传统的 std::thread::spawn 用法。为了确保主线程在所有子线程执行完毕后才退出,我们必须手动创建一个 Vec 来收集每个线程的 JoinHandle,并在最后显式地遍历它们并调用 join()。这种模式是有效的,但代码略显繁琐,且容易因忘记 join 而导致主线程提前结束。

🗺️ 示例三:探索新大陆:thread::scope

use std::{thread, time::Duration};

fn main() {
    thread::scope(|s| {
        for i in 0..5 {
            s.spawn(move || {
                thread::sleep(Duration::from_secs(1));
                println!("Scoped thread: {i}");
            });
        }
    });
}

运行示例三

RustJourney/scoped_threads on  main [?] is 📦 0.1.0 via 🦀 1.88.0 on 🐳 v28.2.2 (orbstack) took 2.1s 
➜ cargo run
   Compiling scoped_threads v0.1.0 (/Users/qiaopengjun/Code/Rust/RustJourney/scoped_threads)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.58s
     Running `target/debug/scoped_threads`
Scoped thread: 1
Scoped thread: 0
Scoped thread: 2
Scoped thread: 3
Scoped thread: 4

这是 Scoped Threads 的首次亮相。注意看,代码变得多么简洁!我们使用了 thread::scope 创建了一个作用域,所有通过 s.spawn 创建的线程都被限制在这个作用域内。当 scope 闭包执行结束时,Rust 会自动确保所有这些子线程都已完成,我们不再需要手动管理 JoinHandle,代码的可读性和健壮性都得到了提升。

💎 示例四:发现宝藏:安全借用

use std::{thread, time::Duration};

fn main() {
    let a = String::from("Hello");
    thread::scope(|s| {
        for _ in 0..5 {
            s.spawn(|| {
                thread::sleep(Duration::from_secs(1));
                println!("Scoped thread: {a}");
            });
        }
    });
}

运行示例四

RustJourney/scoped_threads on  main [?] is 📦 0.1.0 via 🦀 1.88.0 on 🐳 v28.2.2 (orbstack) took 2.2s 
➜ cargo run
   Compiling scoped_threads v0.1.0 (/Users/qiaopengjun/Code/Rust/RustJourney/scoped_threads)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.70s
     Running `target/debug/scoped_threads`
Scoped thread: Hello
Scoped thread: Hello
Scoped thread: Hello
Scoped thread: Hello
Scoped thread: Hello

这是 Scoped Threads 最强大的优势之一:安全地借用外部变量。在传统 thread::spawn 中,由于线程可能比创建它的函数活得更久,所以闭包必须获得变量的完整所有权(通过 move),且变量必须是 'static 的。但在这里,s.spawn 的闭包可以直接借用 main 函数中的变量 a,甚至不需要 move 关键字!这是因为编译器知道,scope 会确保所有子线程在 a 被销毁前就已结束,因此借用是完全安全的。

🧭 示例五:揭秘指南针:生命周期

use std::{thread, time::Duration};

fn main() {
    // let a = String::from("Hello");

    // ’scope 作用域线程可在此生命周期生成和运行
    // 'scope 生命周期比 scope 函数内闭包的生命周期长
    // 所以作用域线程可能活的比闭包长
    thread::scope(|s| {
        for i in 0..5 {
            s.spawn(move || {
                thread::sleep(Duration::from_secs(1));
                println!("Scoped thread: {i}");
            });
        }
    }); // non-'static
    // 'env 'ENV 生命周期代表 被作用域线程借用的那些数据的生命周期 必须要长于 scope 的调用周期

    // thread::spawn(||); 'static
}

运行示例五

RustJourney/scoped_threads on  main [?] is 📦 0.1.0 via 🦀 1.88.0 on 🐳 v28.2.2 (orbstack) took 2.3s 
➜ cargo run
   Compiling scoped_threads v0.1.0 (/Users/qiaopengjun/Code/Rust/RustJourney/scoped_threads)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.55s
     Running `target/debug/scoped_threads`
Scoped thread: 1
Scoped thread: 3
Scoped thread: 2
Scoped thread: 4
Scoped thread: 0

这段代码通过注释解释了 Scoped Threads 的生命周期原理。'scope 代表了作用域线程可以存活的范围,而 'env 代表了被线程借用的外部环境(如变量 a)的生命周期。Rust 编译器会强制要求 'env 必须比 'scope 更长,从而在编译期就杜绝了悬垂指针的风险,这是 Rust 内存安全性的核心体现。

🏆 示例六:终点:收获并行硕果

use std::thread;

fn main() {
    const CHUNK_SIZE: usize = 10;
    let numbers: Vec<u32> = (1..10000).collect();
    let chunks = numbers.chunks(CHUNK_SIZE);

    let total_sum = thread::scope(|s| {
        let mut handles = Vec::new();

        for chunk in chunks {
            let handle = s.spawn(move || chunk.iter().sum::<u32>());
            handles.push(handle);
        }

        // let mut total_sum = 0;
        // for handle in handles {
        //     total_sum += handle.join().unwrap();
        // }

        // println!("Total sum: {total_sum}"); // Total sum: 49995000

        handles.into_iter().map(|h| h.join().unwrap()).sum::<u32>()
    });

    println!("Total sum: {total_sum}"); // Total sum: 49995000
}

运行示例六

RustJourney/scoped_threads on  main [?] is 📦 0.1.0 via 🦀 1.88.0 on 🐳 v28.2.2 (orbstack) 
➜ cargo run
   Compiling scoped_threads v0.1.0 (/Users/qiaopengjun/Code/Rust/RustJourney/scoped_threads)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
     Running `target/debug/scoped_threads`
Total sum: 49995000

这是一个非常实用和典型的案例。我们将一个大任务(对1到9999的数字求和)分解成许多小块(chunks),然后为每个小块启动一个作用域线程来并行计算部分和。最后,通过 mapsum 将每个线程返回的结果优雅地汇总起来。thread::scope 不仅让并行处理的逻辑变得清晰,还能直接从 scope 块中返回计算结果 total_sum,整个过程一气呵成。

总结

总而言之,std::thread::scope 为 Rust 并发编程带来了巨大的便利性和安全性。它通过作用域自动管理线程的生命周期,免去了手动调用 join 的繁琐和潜在风险。其最大的亮点在于能够安全地从父作用域借用非 'static 数据,极大地简化了许多并行计算场景下的代码实现,如示例六中的分块处理。

虽然作用域线程的生命周期受限,无法创建“分离”的后台线程,但对于大多数需要在特定任务完成后再继续主流程的场景,它都是一个完美且更优的选择。在你的下一个 Rust 项目中,如果遇到需要并行处理数据但又苦于处理所有权和生命周期的问题,请尝试使用 Scoped Threads,它会让你的代码变得更加优雅和健壮。

参考

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论