Rust - 并发
Rust - 并发
Section titled “Rust - 并发”并发编程(Concurrent programming),程序的不同部分独立执行;以及并行编程(parallel programming),它们同时执行,变得越来越重要。Rust 的所有权和类型系统提供了强大的编译时保证,以防止常见并发 bug,如数据竞争(data races)。这使得编写安全的并发代码显著更容易。
1. 创建线程以实现并行执行
Section titled “1. 创建线程以实现并行执行”操作系统管理进程,而在一个进程内部,你可以拥有独立的部分,称为线程(threads),它们可以同时运行。你可以使用 thread::spawn 函数创建一个新线程,该函数接受一个包含要运行代码的闭包。
使用 JoinHandle 等待线程
Section titled “使用 JoinHandle 等待线程”当程序的主线程退出时,新线程将停止,无论它是否完成了工作。为了防止这种情况,thread::spawn 返回一个 JoinHandle。对此 handle 调用 join() 方法将阻塞当前线程,直到 spawned 线程完成。
use std::thread;use std::time::Duration;
fn main() { let handle = thread::spawn(|| { for i in 1..=5 { println!("Hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } });
for i in 1..=3 { println!("Hi number {} from the main thread!", i); thread::sleep(Duration::from_millis(1)); }
// 阻塞主线程,直到 spawned 线程完成。 // unwrap() 会在子线程 panic 时导致 panic。 handle.join().unwrap();
println!("Main thread exiting after spawned thread has finished.");}示例输出(顺序可能不同)
Section titled “示例输出(顺序可能不同)”Hi number 1 from the main thread!Hi number 1 from the spawned thread!Hi number 2 from the main thread!Hi number 2 from the spawned thread!Hi number 3 from the main thread!Hi number 3 from the spawned thread!Hi number 4 from the spawned thread!Hi number 5 from the spawned thread!Main thread exiting after spawned thread has finished.在线程中使用 move 闭包
Section titled “在线程中使用 move 闭包”通常,你会在 spawned 线程中使用来自主线程的数据。为此,你必须使用 move 闭包来强制闭包获取其所用值的所有权。这是一种安全措施,旨在防止主线程在使用 spawned 线程仍在使用数据时使其失效。
use std::thread;
fn main() { let v = vec![1, 2, 3];
// 'move' 关键字将 'v' 的所有权转移给新线程。 let handle = thread::spawn(move || { println!("Here's a vector: {:?}", v); });
// 'v' 已经被移动,因此我们不能再在主线程中使用它。 // drop(v); // 这会导致编译时错误。
handle.join().unwrap();}2. 使用通道进行消息传递
Section titled “2. 使用通道进行消息传递”线程之间安全有效的通信方式是通过通道(channels)。这遵循了“不要通过共享内存来通信;相反,通过通信来共享内存”的原则。一个通道有一个发送端 (tx) 和一个接收端 (rx)。
use std::sync::mpsc; // mpsc 代表多发送者,单接收者use std::thread;
fn main() { // 创建一个新通道 let (tx, rx) = mpsc::channel();
// 创建一个线程来发送消息 thread::spawn(move || { let val = String::from("hi"); println!("Thread sending: {}", val); tx.send(val).unwrap(); // 'val' 现在由通道拥有,不能再在此处使用。 });
// 主线程接收消息。recv() 会阻塞,直到有消息可用。 let received = rx.recv().unwrap(); println!("Main got: {}", received);}Thread sending: hiMain got: hi3. 使用互斥锁(Mutex)的共享状态并发
Section titled “3. 使用互斥锁(Mutex)的共享状态并发”对于必须共享内存的情况,Rust 提供了并发原语,例如 Mutex<T>(互斥锁)。Mutex 允许在任何给定时间只有一个线程访问某些数据。为了在多个线程间共享 Mutex 的所有权,我们将其包装在一个 Arc<T>(原子引用计数)中,它是 Rc<T> 的线程安全版本。
use std::sync::{Mutex, Arc};use std::thread;
fn main() { // Arc<T> 提供对堆上分配的类型 T 值的共享所有权。 let counter = Arc::new(Mutex::new(0)); let mut handles = vec![];
for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { // lock() 获取互斥锁,阻塞当前线程直到可用。 // 当 lock() 返回的 MutexGuard 超出作用域时,锁会被释放。 let mut num = counter.lock().unwrap();
*num += 1; }); handles.push(handle); }
for handle in handles { handle.join().unwrap(); }
println!("Result: {}", *counter.lock().unwrap());}Result: 10使用 async/await 实现现代异步 Rust
Section titled “使用 async/await 实现现代异步 Rust”对于 I/O 密集型任务(如网络请求或文件系统操作),Rust 通过 async 和 await 语法对异步编程提供了第一类支持。async 操作不会阻塞线程,而是会交出控制权,允许其他任务运行。这使得单个线程能够高效地处理许多并发操作。
要运行 async 代码,你需要一个异步运行时(async runtime),例如 tokio 或 async-std。以下是一个概念性示例:
// 此示例需要将 `tokio` crate 添加到 Cargo.toml 中// [dependencies]// tokio = { version = "1", features = ["full"] }
async fn say_hello() { println!("Hello");}
#[tokio::main]async fn main() { // 调用 async 函数会返回一个 Future。 let future = say_hello();
// `.await` 关键字会暂停执行,直到 Future 完成。 future.await;
println!("Async function finished.");}关键要点: Rust 的 Send 和 Sync trait 是标记 trait,编译器使用它们在编译时强制执行线程安全,从而防止了一整类并发错误。