🎬 HoRain云小助手个人主页

 🔥 个人专栏: 《Linux 系列教程》《c语言教程

⛺️生活的理想,就是为了理想的生活!


⛳️ 推荐

前些天发现了一个超棒的服务器购买网站,性价比超高,大内存超划算!忍不住分享一下给大家。点击跳转到网站。

专栏介绍

专栏名称

专栏介绍

《C语言》

本专栏主要撰写C干货内容和编程技巧,让大家从底层了解C,把更多的知识由抽象到简单通俗易懂。

《网络协议》

本专栏主要是注重从底层来给大家一步步剖析网络协议的奥秘,一起解密网络协议在运行中协议的基本运行机制!

《docker容器精解篇》

全面深入解析 docker 容器,从基础到进阶,涵盖原理、操作、实践案例,助您精通 docker。

《linux系列》

本专栏主要撰写Linux干货内容,从基础到进阶,知识由抽象到简单通俗易懂,帮你从新手小白到扫地僧。

《python 系列》

本专栏着重撰写Python相关的干货内容与编程技巧,助力大家从底层去认识Python,将更多复杂的知识由抽象转化为简单易懂的内容。

《试题库》

本专栏主要是发布一些考试和练习题库(涵盖软考、HCIE、HRCE、CCNA等)

目录

⛳️ 推荐

专栏介绍

🧵 一、线程:基础并发单元

📨 二、消息传递:Channel

🔐 三、共享状态:Mutex 和 Arc

⚡ 四、异步编程:async/await

📊 五、数据并行:使用 Rayon

🛡️ 六、安全并发的基础:Send 和 Sync

💡 七、最佳实践与抉择


img

Rust 的并发编程能力是其最引人注目的特性之一,它通过独特的所有权系统和类型安全,让你能够编写出既​​安全​​又​​高效​​的并发程序。下面我将为你梳理 Rust 并发编程的核心概念、实践方式以及最佳实践。

首先,我们通过一个表格快速了解 Rust 中处理并发的几种主要范式:

​并发范式​

​核心机制​

​设计思想​

​典型应用场景​

​线程与共享状态​

std::thread+ Arc<Mutex<T>>

通过锁机制安全地在线程间共享和修改数据

CPU 密集型任务、需要复杂状态共享

​消息传递​

std::sync::mpsc::channel

通过通信来共享内存,线程间通过发送消息进行通信

任务分发、流水线、解耦生产者与消费者

​异步编程​

async/await+ Tokio 运行时

基于 Future 和事件循环,在少量线程上高效处理大量 I/O 操作

高并发网络服务、I/O 密集型应用

​数据并行​

rayon::par_iter

将数据集自动分割并在多个线程上并行处理同一操作

批量数据处理、并行计算


🧵 一、线程:基础并发单元

Rust 的标准库提供了对操作系统线程的原生支持。每个线程都有自己的栈和状态,是并发执行的基本单位。

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

fn main() {
    // 使用 spawn 创建新线程
    let handle = thread::spawn(|| {
        for i in 1..5 {
            println!("新线程打印: {}", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    // 主线程继续执行
    for i in 1..3 {
        println!("主线程打印: {}", i);
        thread::sleep(Duration::from_millis(1));
    }

    // 使用 join 等待新线程结束,确保其完成执行
    handle.join().unwrap();
}

citation:7

​关键点​​:

  • thread::spawn接受一个闭包,并在新线程中执行它。

  • handle.join()会阻塞当前线程,直到关联的新线程执行完毕。这对于确保线程完成和获取其结果至关重要。

  • Rust 的​​所有权机制​​会自动处理线程闭包中捕获变量的生命周期和所有权转移(通常需要使用 move关键字)。


📨 二、消息传递:Channel

Rust 强制实行线程间隔离的所有权,但其标准库提供的 ​​channel(通道)​​ 允许线程间通过发送消息进行通信。

use std::sync::mpsc; // 多生产者,单消费者
use std::thread;

fn main() {
    // 创建一个通道,返回一个发送端(tx)和一个接收端(rx)
    let (tx, rx) = mpsc::channel();

    // 生成一个新线程,并将发送端移动给它
    thread::spawn(move || {
        let val = String::from("hi");
        tx.send(val).unwrap(); // 发送消息
        // println!("val is {}", val); // ❌ 错误!val 的所有权已被发送
    });

    // 在主线程中接收消息
    let received = rx.recv().unwrap(); // recv() 会阻塞线程,try_recv() 不会
    println!("收到: {}", received); // 打印: "收到: hi"
}

citation:6][citation:7

​关键点​​:

  • mpsc::channel()生成一个​​多生产者、单消费者​​的通道。

  • send方法会获取消息的所有权并将其发送到通道中。

  • recv()会阻塞当前线程直到收到消息,try_recv()则立即返回而不阻塞。

  • 消息传递是避免共享状态复杂性和数据竞争的有效方式。


🔐 三、共享状态:Mutex 和 Arc

有时线程间​​共享数据​​是必要的。Rust 的 Mutex(互斥锁)和 Arc(原子引用计数智能指针)是实现线程安全共享状态的经典组合。

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    // 使用 Arc 让多个线程拥有 Mutex 的所有权
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        // 克隆 Arc 以增加引用计数,使新线程能获取其所有权
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            // 获取锁,以访问 Mutex 内部的数据
            let mut num = counter.lock().unwrap(); // lock() 返回一个 LockResult<MutexGuard<T>>
            *num += 1; // 解引用 MutexGuard 来修改内部数据
            // 当 MutexGuard 离开作用域时,锁会自动释放
        });
        handles.push(handle);
    }

    // 等待所有线程完成
    for handle in handles {
        handle.join().unwrap();
    }

    // 打印最终结果
    println!("最终结果: {}", *counter.lock().unwrap()); // 打印: "最终结果: 10"
}

citation:3][citation:7

​关键点​​:

  • Mutex<T>通过其 lock方法提供​​内部可变性​​,一次只允许一个线程访问内部数据。

  • Arc<T>(​​A​​tomic ​​R​​eference ​​C​​ounted)类似于 Rc<T>,但使用原子操作实现引用计数,是​​线程安全​​的。它用于在多线程间共享所有权。

  • 必须处理 lock操作可能返回的 Err(例如,当另一个线程持有锁并发生 panic 时,锁会“中毒”),示例中使用了 unwrap进行简单处理。


⚡ 四、异步编程:async/await

对于 ​​I/O 密集型​​任务(如网络请求、文件读写),创建大量操作系统线程开销很大。Rust 的 ​async/await​ 语法允许你编写非阻塞的、高效的并发代码,通常在少量线程上就能处理大量任务。

​基础示例(使用 Tokio 运行时)​​:

// 在 Cargo.toml 中添加依赖: tokio = { version = "1.0", features = ["full"] }
use tokio::time::{sleep, Duration};

#[tokio::main] // 属性宏,将 main 函数设置为 Tokio 运行时入口
async fn main() {
    // 使用 `async` 定义异步函数
    async fn say_hello_after(delay: u64, name: &str) {
        sleep(Duration::from_secs(delay)).await; // 使用 `.await` 等待异步操作完成而不阻塞线程
        println!("Hello, {}!", name);
    }

    // 使用 `tokio::spawn` 并发运行多个异步任务
    let task1 = tokio::spawn(say_hello_after(2, "Alice"));
    let task2 = tokio::spawn(say_hello_after(1, "Bob"));

    // 等待两个任务完成
    let (_, _) = tokio::join!(task1, task2); // join! 等待所有提供的 Future 完成
}

citation:3][citation:8

​关键点​​:

  • async fnasync block会返回一个实现了 Futuretrait 的值。​​Future 是惰性的​​,只有在通过 .await驱动时才会执行。

  • .await会挂起当前异步任务(而不是阻塞整个线程),让出线程资源以执行其他任务,直到等待的 Future 就绪。

  • ​运行时(Runtime)​​(如 Tokio)负责调度和执行这些 Future。你需要一个运行时来驱动异步代码。

  • 异步编程非常适合处理大量并发的 I/O 操作。


📊 五、数据并行:使用 Rayon

Rayon 是一个流行的库,它可以让你​​轻松地将顺序计算转换为并行计算​​,通常只需更改几行代码。

// 在 Cargo.toml 中添加依赖: rayon = "1.5"
use rayon::prelude::*;

fn main() {
    let mut v = vec![6, 2, 8, 1, 9, 3];
    
    // 使用 par_iter_mut 进行并行迭代和修改
    v.par_iter_mut().for_each(|x| {
        *x *= 2;
    });
    println!("{:?}", v); // 输出: [12, 4, 16, 2, 18, 6]

    // 使用 into_par_iter 消费迭代器并并行处理
    let sum: i32 = (0..1000).into_par_iter().sum();
    println!("Sum: {}", sum);
}

citation:3][citation:11

​关键点​​:

  • Rayon 使用​​工作窃取(work-stealing)​​ 算法来高效地在线程池中分配任务。

  • 它将顺序迭代器(iter)替换为并行迭代器(par_iter, into_par_iter, par_iter_mut),使得并行化几乎毫不费力。

  • 适用于需要处理大量独立数据的场景。


🛡️ 六、安全并发的基础:Send 和 Sync

Rust 能保证线程安全,很大程度上归功于两个关键的标记 trait:

  • Send​:允许类型的所有权在线程间​​转移​​。绝大多数类型都是 Send的,但像 Rc<T>这种非原子引用计数的类型就不是。

  • Sync​:允许类型通过​​不可变引用​​(&T)在线程间​​共享​​。如果一个类型是 Sync,那么 &T就是 Send。像 Mutex<T>这样的类型就是 Sync的。

编译器会自动为合适的类型实现这些 trait。如果你的类型包含非 Send或非 Sync的类型,你需要非常小心地确保其线程安全,或者使用 unsafe手动实现这些 trait。


💡 七、最佳实践与抉择

  1. ​消息传递 vs. 共享状态​

    • 优先考虑​​消息传递​​(Channel),它通常更清晰,更符合 Rust 的所有权哲学,能减少死锁和数据竞争的风险。

    • 当共享状态不可避免时,再使用 ​Arc<Mutex<T>>​ 或 Arc<RwLock<T>>(用于读多写少的场景)。

  2. ​线程 vs. 异步​

    • ​CPU 密集型​​任务:使用 std::thread。创建与 CPU 核心数相近的线程数,或使用 Rayon 的线程池。

    • ​I/O 密集型​​任务(高并发网络、磁盘操作):使用 ​async/await​ 和异步运行时(如 Tokio),能以极少的线程资源处理大量并发任务。

  3. ​避免死锁​

    • 即使有 Rust 的帮助,​​死锁​​(两个或多个线程相互等待对方持有的锁)仍然可能发生。

    • 策略:以固定的全局顺序获取多个锁;使用超时机制的 try_lock;简化锁的持有范围。

  4. ​性能工具​

    • 使用 ​loom​ 库进行并发模型的压力测试和检查。

    • 使用 ​tokio-console​ 来可视化和调试异步任务。

希望这份指南能帮助你理解 Rust 并发编程的核心概念和如何选择正确的工具!

❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄

💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍

🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐