Rust 练习册 10:多线程基础与并发安全
并发编程是现代软件开发中的重要主题,它允许程序同时执行多个任务以提高性能和响应性。Rust 通过其所有权系统和类型系统,在编译时就保证了线程安全,避免了数据竞争等常见并发问题。今天我们就来深入学习 Rust 中的多线程编程基础。
什么是多线程编程?
多线程编程允许程序同时执行多个线程,每个线程都是独立的执行路径。在 Rust 中,线程是轻量级的,由操作系统调度,可以并行执行以提高程序性能。
项目中的示例代码
让我们先看看项目中的示例代码:
use std::{thread, time::Duration};#[test]
fn it_works() {let duration = Duration::from_millis(3000);thread::spawn(move || {thread::sleep(duration);});assert_eq!(duration.as_millis(), 3000);println!("{:?}", duration);
}// fn inner_fn(vref: &mut Vec<u32>) {
// std::thread::spawn(move || {
// vref.push(1);
// });
// }
#[test]
fn inner_it_works() {let mut v = vec![1, 2, 3];// inner_fn(&mut v);
}
在这个示例中,我们使用 thread::spawn 创建了一个新线程,该线程会休眠 3 秒。注意 move 关键字的使用,它将 duration 变量的所有权转移到新线程中。
被注释掉的代码展示了 Rust 如何防止数据竞争:我们不能简单地将可变引用传递给新线程,因为这会违反 Rust 的借用规则。
基本线程操作
创建线程
use std::thread;
use std::time::Duration;fn basic_thread_creation() {// 创建一个新线程let handle = thread::spawn(|| {for i in 1..10 {println!("hi number {} from the spawned thread!", i);thread::sleep(Duration::from_millis(1));}});// 在主线程中执行for i in 1..5 {println!("hi number {} from the main thread!", i);thread::sleep(Duration::from_millis(1));}// 等待新线程完成handle.join().unwrap();
}
使用 move 关键字
use std::thread;fn move_keyword_example() {let v = vec![1, 2, 3];let handle = thread::spawn(move || {println!("Here's a vector: {:?}", v);});handle.join().unwrap();
}
move 关键字将闭包环境中使用的值的所有权转移到新线程中。
线程间通信
使用通道(Channels)
use std::sync::mpsc;
use std::thread;
use std::time::Duration;fn channel_example() {let (tx, rx) = mpsc::channel();thread::spawn(move || {let vals = vec![String::from("hi"),String::from("from"),String::from("the"),String::from("thread"),];for val in vals {tx.send(val).unwrap();thread::sleep(Duration::from_secs(1));}});for received in rx {println!("Got: {}", received);}
}
多生产者通道
use std::sync::mpsc;
use std::thread;
use std::time::Duration;fn multiple_producers_example() {let (tx, rx) = mpsc::channel();let tx1 = tx.clone();thread::spawn(move || {let vals = vec![String::from("hi"),String::from("from"),String::from("the"),String::from("thread"),];for val in vals {tx1.send(val).unwrap();thread::sleep(Duration::from_secs(1));}});thread::spawn(move || {let vals = vec![String::from("more"),String::from("messages"),String::from("for"),String::from("you"),];for val in vals {tx.send(val).unwrap();thread::sleep(Duration::from_secs(1));}});for received in rx {println!("Got: {}", received);}
}
共享状态并发
使用互斥锁(Mutex)
use std::sync::{Arc, Mutex};
use std::thread;fn mutex_example() {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!("Result: {}", *counter.lock().unwrap());
}
使用原子类型
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;fn atomic_example() {let counter = Arc::new(AtomicUsize::new(0));let mut handles = vec![];for _ in 0..10 {let counter = Arc::clone(&counter);let handle = thread::spawn(move || {counter.fetch_add(1, Ordering::SeqCst);});handles.push(handle);}for handle in handles {handle.join().unwrap();}println!("Result: {}", counter.load(Ordering::SeqCst));
}
线程安全的智能指针
Arc(原子引用计数)
use std::sync::Arc;
use std::thread;fn arc_example() {let data = Arc::new(vec![1, 2, 3, 4]);let mut handles = vec![];for i in 0..4 {let data_clone = Arc::clone(&data);let handle = thread::spawn(move || {println!("Thread {}: {:?}", i, data_clone);});handles.push(handle);}for handle in handles {handle.join().unwrap();}
}
实际应用示例
并行计算
use std::sync::{Arc, Mutex};
use std::thread;fn parallel_computation_example() {let numbers: Vec<i32> = (1..=1000).collect();let sum = Arc::new(Mutex::new(0));let mut handles = vec![];let chunk_size = numbers.len() / 4;for chunk in numbers.chunks(chunk_size) {let chunk = chunk.to_vec();let sum_clone = Arc::clone(&sum);let handle = thread::spawn(move || {let chunk_sum: i32 = chunk.iter().sum();let mut total = sum_clone.lock().unwrap();*total += chunk_sum;});handles.push(handle);}for handle in handles {handle.join().unwrap();}println!("Sum: {}", *sum.lock().unwrap());
}
生产者-消费者模式
use std::sync::mpsc;
use std::thread;
use std::time::Duration;fn producer_consumer_example() {let (tx, rx) = mpsc::channel();// 生产者线程let producer = thread::spawn(move || {for i in 0..10 {println!("Producing item {}", i);tx.send(i).unwrap();thread::sleep(Duration::from_millis(500));}});// 消费者线程let consumer = thread::spawn(move || {for received in rx {println!("Consuming item {}", received);thread::sleep(Duration::from_millis(750));}});producer.join().unwrap();consumer.join().unwrap();
}
错误处理与线程
处理线程中的 panic
use std::thread;fn panic_handling_example() {let handle = thread::spawn(|| {panic!("Oops!");});match handle.join() {Ok(_) => println!("Thread completed successfully"),Err(_) => println!("Thread panicked"),}
}
返回结果的线程
use std::thread;fn result_thread_example() {let handle = thread::spawn(|| -> Result<i32, &'static str> {// 一些可能失败的操作Ok(42)});match handle.join() {Ok(Ok(result)) => println!("Result: {}", result),Ok(Err(err)) => println!("Error: {}", err),Err(_) => println!("Thread panicked"),}
}
最佳实践
1. 合理使用线程
use std::thread;
use std::time::Duration;// 好的做法:为计算密集型任务使用线程
fn good_thread_usage() {let handle = thread::spawn(|| {// CPU 密集型计算let result: u128 = (1..1_000_000).map(|x| x * x).sum();result});// 在等待时做其他工作println!("Doing other work while waiting...");let result = handle.join().unwrap();println!("Result: {}", result);
}// 避免:为简单任务创建过多线程
fn avoid_excessive_threads() {// 不好的做法:为每个简单操作创建线程// let handles: Vec<_> = (0..1000)// .map(|i| {// thread::spawn(move || i * 2)// })// .collect();
}
2. 正确处理所有权
use std::sync::{Arc, Mutex};
use std::thread;fn ownership_example() {let data = Arc::new(Mutex::new(vec![1, 2, 3, 4, 5]));let mut handles = vec![];// 正确:克隆 Arc 来共享所有权for i in 0..3 {let data_clone = Arc::clone(&data);let handle = thread::spawn(move || {let mut vec = data_clone.lock().unwrap();vec.push(i);});handles.push(handle);}for handle in handles {handle.join().unwrap();}println!("Final data: {:?}", *data.lock().unwrap());
}
与项目代码的深入分析
让我们回到原始项目代码,深入分析其中的概念:
use std::{thread, time::Duration};#[test]
fn it_works() {let duration = Duration::from_millis(3000);thread::spawn(move || {thread::sleep(duration);});assert_eq!(duration.as_millis(), 3000);println!("{:?}", duration);
}
这段代码展示了:
- 使用
move关键字将duration的所有权转移到新线程 - 在新线程中使用
thread::sleep模拟耗时操作 - 主线程继续执行而不等待子线程完成
被注释的代码揭示了 Rust 的一个重要特性:
// fn inner_fn(vref: &mut Vec<u32>) {
// std::thread::spawn(move || {
// vref.push(1);
// });
// }
这段代码无法编译,因为:
&mut Vec<u32>是一个可变引用- 将可变引用移动到新线程会违反 Rust 的借用规则
- 这会可能导致数据竞争
正确的做法是使用 Arc<Mutex<T>>:
use std::sync::{Arc, Mutex};
use std::thread;fn correct_shared_mutable_state() {let mut v = vec![1, 2, 3];let shared_v = Arc::new(Mutex::new(v));let shared_v_clone = Arc::clone(&shared_v);let handle = thread::spawn(move || {let mut vec = shared_v_clone.lock().unwrap();vec.push(1);});handle.join().unwrap();println!("Vector: {:?}", *shared_v.lock().unwrap());
}
总结
Rust 的多线程编程通过其所有权系统提供了强大的安全保障:
thread::spawn创建新线程move关键字转移所有权到新线程- 通道(Channels)提供线程间安全通信
Mutex和Arc允许安全的共享状态- 原子类型提供无锁并发操作
关键要点:
- Rust 在编译时防止数据竞争
- 所有权系统确保线程安全
- 通过
move关键字明确所有权转移 - 使用标准库提供的同步原语处理共享状态
通过合理使用这些特性,我们可以编写出既高效又安全的并发程序。
