这是【零基础 Rust 入门】系列的第 4 章。本系列由前端技术专家零弌分享。想要探索前端技术的无限可能,就请关注我们吧!🤗
- 🧑💻 我们是谁:支付宝体验技术部-基础服务团队
- 📖 我们的专栏:前沿视点
简介
“Fearless Concurrency” —— 无畏并发,让并发编程安全、高效是 Rust 的另一大目标,期望并发问题通过 owership 和生命周期在编译期就能重现,而不是在运行期间艰难的猜测,因为任何 debug 行为包括日志、断点都可能导致问题无法重现。
线程
thread::spawn
通过 thread::spawn
即可 fork 一个线程,执行并发操作。
use std::thread;
use std::time::Duration;
#[derive(Debug)]
enum MyError {}
fn main() {
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));
}
}
并行输出
hi number 1 from the main thread!
hi number 1 from the spawned thread!
hi number 2 from the main thread!
hi number 2 from the spawned thread!
hi number 3 from the main thread!
hi number 3 from the spawned thread!
hi number 4 from the main thread!
hi number 4 from the spawned thread!
hi number 5 from the spawned thread!
handler.join()
通过 join
方法等待 thread 执行完成。
use std::thread;
use std::time::Duration;
fn main() {
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();
}
并行输出
hi number 1 from the main thread!
hi number 1 from the spawned thread!
hi number 2 from the main thread!
hi number 2 from the spawned thread!
hi number 3 from the main thread!
hi number 3 from the spawned thread!
hi number 4 from the main thread!
hi number 4 from the spawned thread!
hi number 5 from the spawned thread!
hi number 6 from the spawned thread!
hi number 7 from the spawned thread!
hi number 8 from the spawned thread!
hi number 9 from the spawned thread!
线程返回结果
join
方法签名为 fn join(self) -> Result<T>
,其中 T
就是线程的返回值,比如下面代码块的 T
就是 std::result::Result<i32, MyError>
。
use std::thread;
use std::time::Duration;
#[derive(Debug)]
enum MyError {}
fn main() {
let handle = thread::spawn(|| -> std::result::Result<i32, MyError> {
let mut count = 0;
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
count += 1;
}
Ok(count)
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
match handle.join().unwrap() {
Ok(count) => {
println!("thread loop {:?}", count);
}
Err(e) => {
println!("thread never error {:?}", e);
}
}
}
处理线程 panic
join
返回的 Result 中 Err 的情况就是线程 panic。
use std::thread;
use std::time::Duration;
#[derive(Debug)]
enum MyError {}
fn main() {
let handle = thread::spawn(|| -> std::result::Result<i32, MyError> {
let mut count = 0;
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
count += 1;
}
panic!("mock panic");
Ok(count)
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
match handle.join() {
Ok(Ok(count)) => {
println!("thread loop {:?}", count);
}
Ok(Err(e)) => {
println!("thread never error {:?}", e);
}
// 这里处理了线程 panic 的情况
Err(e) => {
println!("thread failed!");
}
}
}
输出
thread '<unnamed>' panicked at src/main.rs:15:9:
mock panic
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread failed!
内存处理的三种姿势
move
使用 move
捕获闭包外的对象,否则默认捕获的是 reference。
use std::thread;
fn main() {
let vec = vec!(1, 2, 3, 4, 5, 6, 7, 8, 9);
let handle = thread::spawn(move || -> i32 {
vec.into_iter().reduce(|acc, e| acc + e).unwrap()
});
match handle.join() {
Ok(sum) => {
println!("sum is {:?}", sum);
}
Err(e) => {
println!("thread failed!");
}
}
}
mutex
通过 mutex
来实现,mutex 是互斥锁,lock 会锁定直到获取到锁。
问:为什么只看到了 lock 没看到 unlock?
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let vec = Arc::new(vec!(1, 2, 3, 4, 5, 6, 7, 8, 9));
let sum = Arc::new(Mutex::new(0));
let handle1 = {
let sum = sum.clone();
let vec = vec.clone();
thread::spawn(move || {
let vec = &vec[0..4];
for t in vec.iter() {
// 获取 sum 锁
let mut sum = sum.lock().unwrap();
*sum = *sum + t
}
})
};
let handle2 = {
let sum = sum.clone();
let vec = vec.clone();
thread::spawn(move || {
let vec = &vec[4..];
for t in vec.iter() {
// 获取 sum 锁
let mut sum = sum.lock().unwrap();
*sum = *sum + t
}
})
};
handle1.join();
handle2.join();
println!("sum: {:?}", sum.lock());
}
不同的 Lock
- RWLock
- OnceLock
mpsc
multi producer single consumer,tx 可以调用 clone,即可在不同的地方使用。
use std::sync::mpsc;
fn main() {
let vec = vec!(1, 2, 3, 4, 5, 6, 7, 8, 9);
let (tx, rx) = mpsc::channel::<i32>();
let handle = std::thread::spawn(move || {
let mut sum = 0;
// consumer 消费过程
while let Ok(t) = rx.recv() {
sum += t;
}
sum
});
// producer 生成过程
for t in vec {
tx.send(t);
}
drop(tx);
let sum = handle.join().unwrap();
println!("sum: {:?}", sum);
}
等待的多种姿势
Barrier
使所有的线程一起开始
use std::sync::{Arc, Barrier};
use std::thread;
fn main() {
let n = 10;
let mut handles = Vec::with_capacity(n);
let barrier = Arc::new(Barrier::new(n));
for _ in 0..n {
let c = Arc::clone(&barrier);
handles.push(thread::spawn(move|| {
println!("before wait");
c.wait();
println!("after wait");
}));
}
for handle in handles {
handle.join().unwrap();
}
}
Condvar
等待 Condvar 通知,需要和 mutex 配合使用。
以下代码在主线程中会运行一个线程,并等待其开始运行。
use std::sync::{Arc, Mutex, Condvar};
use std::thread;
use std::time::Duration;
fn main() {
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = Arc::clone(&pair);
thread::spawn(move|| {
let (lock, cvar) = &*pair2;
// 通知第一次
cvar.notify_one();
std::thread::sleep(Duration::from_millis(10));
let mut started = lock.lock().unwrap();
println!("set started");
*started = true;
// 通知第二次
cvar.notify_one();
});
let (lock, cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
println!("started: {:?}", *started);
started = cvar.wait(started).unwrap();
}
}
Park
use std::thread;
use std::sync::{Arc, atomic::{Ordering, AtomicBool}};
use std::time::Duration;
fn main() {
let flag = Arc::new(AtomicBool::new(false));
let flag2 = Arc::clone(&flag);
let parked_thread = thread::spawn(move || {
// 等待 flag 被设置为 true
while !flag2.load(Ordering::Relaxed) {
println!("Parking thread");
// 主动挂起进程
thread::park();
// 线程被唤醒了,可能是 Unpark 了
// 也可能是操作系统主动调度
println!("Thread unparked");
}
println!("Flag received");
});
thread::sleep(Duration::from_millis(10));
// 设置 flag
flag.store(true, Ordering::Relaxed);
println!("Unpark the thread");
// unpack
parked_thread.thread().unpark();
parked_thread.join().unwrap();
}
-
Barrier:屏障,所有线程等待一起执行。
-
Condvar: 条件变量,多个线程等待变量执行。
-
Park/Unpark: 暂停/挂起,更加底层。