Rust中的并发与多线程:编写安全的并行代码
Rust是少数能在编译阶段解决数据竞争(data races)问题的语言之一。得益于所有权(ownership)和借用(borrowing)系统,Rust中的并发代码不仅快速,而且安全。本文将讲解Rust多线程的基础知识:创建线程、通过通道和互斥锁进行数据交换,并介绍"Fearless Concurrency"模型。
1. 多线程基础:std::thread
Rust通过std::thread模块提供内置的线程支持。可以使用thread::spawn函数创建新线程,该函数接受一个闭包。
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(); // 等待线程结束}join()方法会阻塞主线程,直到子线程执行完毕。如果没有它,程序可能会在线程执行完之前就结束。
2. 线程间数据传输:通道(mpsc)
在Rust中,线程间消息传递使用多生产者,单消费者通道(mpsc — multiple producer, single consumer)模型。这通过std::sync::mpsc模块实现。
use std::sync::mpsc;use std::thread;
fn main() { let (tx, rx) = mpsc::channel();
thread::spawn(move || { let val = String::from("来自线程的问候!"); tx.send(val).unwrap(); // val 已经被移动,不能再使用 });
let received = rx.recv().unwrap(); println!("收到: {}", received);}注意闭包中的move关键字——它将变量tx的所有权转移到新线程中。send()方法返回一个Result,如果接收端已关闭,则可能为Err。
要创建多个生产者,可以克隆tx:
let (tx, rx) = mpsc::channel();let tx1 = tx.clone();
thread::spawn(move || { tx.send("消息 1").unwrap();});
thread::spawn(move || { tx1.send("消息 2").unwrap();});
for received in rx { println!("收到: {}", received);}3. 共享内存:Mutex和Arc
通道适合数据传输,但有时需要在线程间共享状态。这时使用Mutex(互斥锁)配合Arc(原子引用计数)。
use std::sync::{Arc, Mutex};use std::thread;
fn main() { 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 || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); }
for handle in handles { handle.join().unwrap(); }
println!("结果: {}", *counter.lock().unwrap());}为什么用Arc而不是Rc?普通的Rc不是线程安全的,因为它的引用计数不是原子的。Arc使用原子操作,使其在多线程环境下安全。
重要:Mutex在调用lock()时会阻塞线程。离开作用域后,锁会自动释放(得益于Drop trait)。