【零基础 Rust 入门 04】并发 - 十亿行挑战 1️⃣🐝🏎️

651 阅读5分钟

这是【零基础 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: 暂停/挂起,更加底层。

本章任务

1brc