1. Trait 约束
Send
Trait:
- 当闭包跨线程传递时,闭包捕获的数据必须实现
Send
trait。Send
trait 标记类型可以安全地在线程间传递。例如,如果闭包捕获了一个 Vec<i32>
,Vec<i32>
实现了 Send
,所以可以跨线程传递。
- 代码示例:
use std::thread;
fn main() {
let data = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Data in thread: {:?}", data);
});
handle.join().unwrap();
}
Sync
Trait:
- 如果闭包操作的数据是共享的(例如通过
Arc
共享),那么数据类型必须实现 Sync
trait。Sync
trait 标记类型可以安全地在多个线程间共享不可变引用。例如,Arc<Mutex<i32>>
中,Mutex<i32>
实现了 Sync
,所以可以在多个线程间共享。
- 代码示例:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let shared_data = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let data = shared_data.clone();
let handle = thread::spawn(move || {
let mut num = data.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Final value: {}", *shared_data.lock().unwrap());
}
2. 生命周期问题
- 闭包捕获数据的生命周期:
- 当闭包跨线程传递时,捕获的数据的生命周期必须至少和闭包在新线程中执行的时间一样长。如果捕获的数据是局部变量,使用
move
语义将所有权转移给闭包可以确保数据在新线程执行期间有效。
- 代码示例:
use std::thread;
fn main() {
let s = String::from("hello");
let handle = thread::spawn(move || {
println!("{}", s);
});
handle.join().unwrap();
}
- 共享数据的生命周期:
- 对于通过
Arc
等共享的同步数据,要确保 Arc
的生命周期足够长。例如,如果 Arc
被包含在一个局部变量中,并且在闭包执行之前局部变量超出作用域,会导致悬垂引用。
- 代码示例:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let shared_data;
{
let inner_data = Arc::new(Mutex::new(0));
shared_data = inner_data.clone();
let handle = thread::spawn(move || {
let mut num = shared_data.lock().unwrap();
*num += 1;
});
handle.join().unwrap();
}
// 这里如果尝试访问 shared_data 会导致错误,因为 inner_data 已超出作用域
}
3. 可能遇到的陷阱及避免方法
- 数据竞争:
- 陷阱:如果没有正确使用同步原语(如
Mutex
、RwLock
等),会导致数据竞争。例如,多个线程同时尝试修改共享的非同步数据。
- 避免方法:始终使用实现了同步的类型(如
Arc<Mutex<T>>
或 Arc<RwLock<T>>
)来保护共享数据,确保在任何时刻只有一个线程可以修改数据。
- 死锁:
- 陷阱:死锁可能发生在多个线程互相等待对方释放锁的情况下。例如,线程 A 持有锁 L1 并尝试获取锁 L2,而线程 B 持有锁 L2 并尝试获取锁 L1。
- 避免方法:
- 尽量减少锁的持有时间,尽快释放锁。
- 确保所有线程获取锁的顺序一致。例如,可以总是先获取锁 L1,再获取锁 L2,这样可以避免循环等待。
- 未实现
Send
或 Sync
:
- 陷阱:如果数据类型没有实现
Send
或 Sync
trait,尝试跨线程传递或共享会导致编译错误。
- 避免方法:确保所有被闭包捕获和操作的数据类型都实现了相应的
Send
和 Sync
trait。如果类型本身没有实现,可以考虑使用 unsafe
代码来手动实现,但这需要非常小心,因为错误的实现可能导致未定义行为。