Rust中的并发:从互斥锁到async/await
当谈到高性能系统时,Rust 立即成为焦点。但真正让它独一无二的是什么?这就是 Rust中的并发 — 一种让您获得C语言速度和内存安全,同时又不牺牲代码可读性的方法。在本文中,我们将解析三种主要工具:经典的线程、轻量级的协程(在Rust上下文中指异步任务)以及现代的async/await。开始吧!
为什么Rust不惧怕并发?
与许多语言不同,Rust 在编译器层面解决了数据竞争问题。所有权(ownership)和借用(borrowing)系统确保您不会意外地从两个线程同时修改数据。这不是魔法 — 这是严格的静态检查。正因如此,Rust中的多线程被认为是最安全的之一。
1. 线程 (std::thread)
经典的OS线程是基础。在Rust中,它们通过 std::thread 模块实现。每个线程都是一个独立的轻量级进程,可以在不同的CPU核心上并行执行。
示例:启动线程
use std::thread;use std::time::Duration;
fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("线程: {}", i); thread::sleep(Duration::from_millis(1)); } });
for i in 1..5 { println!("主线程: {}", i); thread::sleep(Duration::from_millis(1)); }
handle.join().unwrap();}这里 thread::spawn 创建一个新线程,而 join 等待其完成。很简单,但有一个问题:线程很“重”。每个线程至少消耗2-8 MB的栈内存。对于数千个并发任务来说,这并不合适。
何时使用线程?
- 需要真正的并行(CPU密集型任务)。
- 线程数量不超过核心数 * 2。
- 处理阻塞操作(文件读取、同步调用)。
2. Rust中的协程:神话还是现实?
严格来说,Rust 没有像Go语言那样的协程。但有异步任务(tasks),它们的行为几乎相同:轻量级、协作式切换(无需OS参与),并且可以在一个线程上启动数千个。为此,需要使用运行时 Tokio 或 async-std。
示例:通过Tokio的异步任务(协程)
use tokio::task;
#[tokio::main]async fn main() { let handle = task::spawn(async { for i in 1..10 { println!("协程: {}", i); tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; } });
for i in 1..5 { println!("主任务: {}", i); tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; }
handle.await.unwrap();}请注意:task::spawn 创建一个轻量级任务,它在Tokio的线程池中执行。每个任务的内存开销约为2-3 KB。这使得可以轻松启动100,000个“协程”。与Go不同,这里没有内置的调度器 — 您自己选择运行时。
Rust协程与Go协程有何不同?
- 在Rust中 — 任务是协作式的:它们通过
.await主动让出控制权。 - 在Go中 — 协程是抢占式的:调度器在I/O点自动中断它们。
- Rust 提供更多控制:您可以选择单线程或多线程运行时。
3. Async/await:异步Rust的核心
关键字 async 和 .await 将同步代码转换为异步代码,而无需