并发编程是现代软件开发中的重要主题,它允许程序同时执行多个任务以提高性能和响应性。Rust 通过其所有权系统和类型系统,在编译时就保证了线程安全,避免了数据竞争等常见并发问题。今天我们就来深入学习 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 的借用规则。
基本线程操作
创建线程
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 关键字
rust
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)
rust
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);
}
}
多生产者通道
rust
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)
rust
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());
}
使用原子类型
rust
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(原子引用计数)
rust
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();
}
}
实际应用示例
并行计算
rust
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());
}
生产者-消费者模式
rust
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
rust
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"),
}
}
返回结果的线程
rust
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. 合理使用线程
rust
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. 正确处理所有权
rust
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());
}
与项目代码的深入分析
让我们回到原始项目代码,深入分析其中的概念:
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);
}
这段代码展示了:
- 使用
move关键字将duration的所有权转移到新线程 - 在新线程中使用
thread::sleep模拟耗时操作 - 主线程继续执行而不等待子线程完成
被注释的代码揭示了 Rust 的一个重要特性:
rust
// fn inner_fn(vref: &mut Vec<u32>) {
// std::thread::spawn(move || {
// vref.push(1);
// });
// }
这段代码无法编译,因为:
&mut Vec<u32>是一个可变引用- 将可变引用移动到新线程会违反 Rust 的借用规则
- 这会可能导致数据竞争
正确的做法是使用 Arc<Mutex<T>>:
rust
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关键字明确所有权转移 - 使用标准库提供的同步原语处理共享状态
通过合理使用这些特性,我们可以编写出既高效又安全的并发程序。