1. 线程的创建
use std::thread;
use std::thread::sleep;
use std::time;
// 通过 thread 库来创建线程
fn main() {
for i in 0..5 {
println!("main: {}", i);
};
let t1 = thread::spawn(|| {
for i in 0..10 {
sleep(time::Duration::from_millis(100));
println!("thread: {}", i);
}
});
t1.join().unwrap();
}
// thread::spawn() 函数返回的是 pub struct JoinHandle<T>(JoinInner<'static, T>);
JoinHandle<T> 的主要功能
- 等待线程完成:
JoinHandle<T>提供了join()方法,该方法会阻塞当前线程,直到关联的线程完成执行。这确保了主线程可以等待子线程完成,从而避免了资源的竞争和数据的不一致。 - 获取线程返回值: 当线程完成执行时,
JoinHandle<T>可以捕获该线程的返回值。这是通过泛型参数T来实现的,它代表了线程的返回值类型。通过调用join()方法,可以获取到线程的返回值。 - 错误处理: 如果线程在执行过程中出现了 panic,
JoinHandle<T>会捕获该异常并将其转换为std::thread::Result<T>类型的错误。在调用join()方法时,可以处理这个错误,从而确保程序的健壮性。
2. 线程之间共享只读数据
// 1. 对于实现了 Copy trait 的数据,只需要借助 move 关键字,便可在多线程之间共享它
fn main() {
let data = 66;
for i in 0..5 {
thread::spawn(move || {
println!("{} : {:?}", i, data);
})
.join()
.unwrap();
}
println!("{:?}", data);
}
// 2. 对于没有实现 Copy trait 的数据,由于第一次 move 后所有权被转移到了线程内。如果要实现多线程共享同一份资源,有 2 种方式:
// * 为每个线程重新 clone 一份数据
// * 利用 Arc clone 引用,让多个引用指向同一份实例
fn main() {
let data = vec![1, 2, 3];
let data2 = Arc::new(vec![5, 6, 7]);
for i in 0..5 {
let data = data.clone(); // 每个线程都 clone 一份数据,相当于堆上会有 5 份数据
let data2 = data2.clone(); // 每个线程都 clone 一份引用,堆上只会有 1 份数据,有 5 个引用都指向这一份数据
thread::spawn(move || {
println!("{} : {:?}", i, data);
println!("{} : {:?}", i, data2);
})
.join()
.unwrap();
}
}
3. 线程之间共享可写数据
主要讲一下 clone 多份引用的时候怎么去更改那一份单独的数据。
// 通过 Mutex 智能指针,能够为单独的那一份数据加锁,且离开线程后由于 Drop tait 的作用,会自动解锁
fn main() {
let data = Arc::new(Mutex::new(vec![5, 6, 7]));
for i in 0..5 {
let data = data.clone();
thread::spawn(move || {
data.lock().unwrap().push(i);
println!("{} : {:?}", i, data.lock().unwrap());
})
.join()
.unwrap();
}
println!("{:?}", data.lock().unwrap()); // [5, 6, 7, 0, 1, 2, 3, 4]
}
4. 线程之间通信
线程之间通信主要是根据通道来实现的 std::sync::mpsc::channel
fn main() {
// s 和 r 的模型是多对一的,即多个生产者一个消费者。 所以 sender 上是有 clone 的方法的,而 receiver 是没有的。
// PS: 只针对标准库里的 channel, 其他库可能会有其它的关系
let (sender, receiver) = channel();
for _i in 0..10 {
let s = sender.clone();
thread::spawn(move || {
for i in 0..10 {
sleep(time::Duration::from_millis(100));
// 巧妙在于,把 sender move 进来后,线程走完,sender 会被自动回收,
// 而 receiver 接收不到数据,就会返回 Result<_, Error>
s.send(i).unwrap();
}
});
}
// receiver 的结束方式是所有的 sender 都被销毁,就会返回 error
// 此时因为用了 clone,在线程里销毁了所有 clone 的,而最外面的 sender 没有 move 进去,
// sender 没有全部销毁,此时需要调用一下 drop
drop(sender);
while let Ok(a) = receiver.recv() {
println!("{}", a);
}
}
5. 总结
Rust 由于其所有权的特性,在多线程操作一些没有实现 Copy trait 的数据时需要借用一些智能指针来实现。
Rust 多线程通信借用的 Go 的方法 “不要通过共享内存来通信,而应该通过通信来共享内存”。