Rust 在设计之初就十分重视并发安全(Concurrency Safety),其核心目标是:在不牺牲性能的前提下,杜绝数据竞争(Data Race)
相比于传统语言中容易出现的线程共享与同步问题,Rust 通过所有权系统类型检查,在编译阶段就能保证并发代码的正确性。

本文将从基础概念入手,深入解析 Rust 的并发模型、常见的并发工具、以及实际应用示例。

一、并发与并行的区别

Rust 官方文档中强调区分两个重要概念:

  • 并发(Concurrency):多个任务在同一时间段交替执行,例如 I/O 操作与计算任务交替。

  • 并行(Parallelism):多个任务真正同时运行,通常需要多核 CPU 支持。

Rust 的并发模型涵盖了这两者:
通过 std::thread 模块提供线程级并行;
通过异步 async/await 模型提供任务级并发。

二、线程基础:std::thread

Rust 提供了轻量级线程创建方式:

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

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..5 {
            println!("子线程输出:{}", i);
            thread::sleep(Duration::from_millis(100));
        }
    });

    for i in 1..5 {
        println!("主线程输出:{}", i);
        thread::sleep(Duration::from_millis(50));
    }

    handle.join().unwrap();
}

解析:

  • thread::spawn 用于创建新线程。

  • join() 用于等待子线程执行结束。

  • Rust 会强制要求线程间共享数据时必须是安全的。

三、线程间通信:通道(Channel)

Rust 借鉴了 Go 的设计,引入了**消息传递(Message Passing)**模型。

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let message = String::from("Hello from thread!");
        tx.send(message).unwrap();
    });

    let received = rx.recv().unwrap();
    println!("主线程接收到:{}", received);
}

解析:

  • mpsc::channel() 表示 multi-producer, single-consumer,即多发送者单接收者模型。

  • send()recv() 用于跨线程传递所有权安全的数据。

  • Rust 编译器确保发送的数据类型实现了 Send trait,从而避免潜在的数据竞争。

四、共享状态与互斥锁

Rust 不提倡共享状态,但当确实需要时,提供了安全的同步原语:

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

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..5 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

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

    println!("最终计数结果:{}", *counter.lock().unwrap());
}

解析:

  • Arc 是多线程安全的引用计数指针(Atomic Reference Counted)。

  • Mutex 提供互斥访问机制。

  • Rust 的类型系统保证只有当持有锁的线程释放后,其他线程才能访问数据。

五、异步并发模型(Async/Await)

Rust 的异步模型基于 Future Trait任务调度器(Executor)
异步任务不会创建真实的操作系统线程,而是通过事件循环(Event Loop)来轮询执行。

use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    let t1 = async_task("任务1");
    let t2 = async_task("任务2");

    futures::join!(t1, t2);
}

async fn async_task(name: &str) {
    for i in 1..=3 {
        println!("{} - 步骤 {}", name, i);
        sleep(Duration::from_millis(100)).await;
    }
}

解析:

  • 使用 tokio 运行时执行异步任务。

  • join! 可并行等待多个任务。

  • 异步任务间不会产生竞争,因为它们共享的是不可变状态,且执行时机由调度器严格控制。

六、Send 与 Sync Trait

Rust 的并发安全核心在于两个特殊 trait:

  • Send:允许类型在不同线程间传递所有权。

  • Sync:允许类型的引用在多个线程中安全共享。

例如:

  • Arc<T> 实现了 SendSync

  • Rc<T> 仅在单线程中安全(不实现 Send)。

编译器会根据这些 trait 自动检测线程安全性,防止出现数据竞争。

七、总结

Rust 的并发系统并非“让你更容易写出并发代码”,而是让错误的并发代码无法通过编译
借助所有权、生命周期与 trait 系统,Rust 构建了一套零成本抽象的并发模型,使得开发者能够编写高性能且安全的并行程序。

在下一篇中,我们将继续深入探讨 异步 I/O 与 Future 执行原理,进一步理解 Rust 如何在性能与安全之间取得平衡。

Logo

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

更多推荐