先看一个程序,看看有什么问题
use core::time;
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
#[derive(PartialEq, Eq)]
enum which {
thread1,
thread2,
}
struct CondState {
count: i32,
turn: which,
}
pub fn run_cvar() {
let pair = Arc::new((
Mutex::new(CondState {
count: 0,
turn: which::thread1,
}),
Condvar::new(),
));
let pair1 = pair.clone();
let pair2 = pair.clone();
let handle1 = thread::spawn(move || printling1(pair1));
let handle2 = thread::spawn(move || printling2(pair2));
handle1.join();
handle2.join();
}
pub fn printling1(state: Arc<(Mutex<CondState>, Condvar)>) {
let (lock, cvar) = &*state;
loop {
let mut shared = lock.lock().unwrap();
if shared.count < 2000 && shared.turn != which::thread1 {
shared = cvar.wait(shared).unwrap();
}
if shared.count >= 2000 {
break;
}
println!("thread1: {}", shared.count);
shared.count += 1;
shared.turn = which::thread2;
cvar.notify_all();
thread::sleep(time::Duration::from_secs(1));
}
}
pub fn printling2(state: Arc<(Mutex<CondState>, Condvar)>) {
let (lock, cvar) = &*state;
loop {
let mut shared = lock.lock().unwrap();
// 如果不是当前线程并且打印次数没有超过2000次就释放锁并等待
if shared.count < 2000 && shared.turn != which::thread2 {
shared = cvar.wait(shared).unwrap();
}
if shared.count >= 2000 {
break;
}
println!("thread1: {}", shared.count);
shared.count += 1;
shared.turn = which::thread1;
// 通知其他线程
cvar.notify_all();
thread::sleep(time::Duration::from_secs(1));
}
}
#[cfg(test)]
mod test {
use super::*;
mod thread_test {
#[test]
fn test_print() {
super::run_cvar();
}
}
}
上述代码运行时可能会遇到问题,简单来说会遇到多线程环境下的虚假唤醒情况
虚假唤醒(Spurious Wakeup)是多线程编程中一个非常经典的底层现象。 简单来说,就是一个处于等待(阻塞)状态的线程,在没有任何其他线程发出通知(Signal/Notify)的情况下,自己莫名其妙地醒过来了。
很多人会觉得这是操作系统或者 Rust 的 Bug,但实际上它是为了追求极致性能而做出的工程妥协。原因主要有以下几点:
如果你在代码中没有防范虚假唤醒,程序就会产生随机的、难以调试的并发 Bug。 回到我们之前“交替打印 A 和 B”的例子:
正是因为虚假唤醒无法在操作系统层面完全避免,所有主流编程语言(Rust, C++, Java, Go)在处理条件变量时,都达成了一个铁律:永远不要用 if 来判断条件,必须使用 while 循环!
// 假设遭遇虚假唤醒,线程醒来后直接往下走,导致逻辑错误
if !started.1 {
started = cvar.wait(started).unwrap();
}// 醒来后直接干活(危险!)
do_something();
// 即使被虚假唤醒了,线程会立刻重新执行 while 条件检查// 发现 !started.1 依然成立(确实没轮到我),就会再次乖乖躺下睡觉
while !started.1 {
started = cvar.wait(started).unwrap();
}// 只有条件真正满足时,才能安全地走到这里
do_something();
由于写 while 循环太普遍了,Rust 标准库干脆直接提供了一个叫 wait_while 的方法。它在底层自动帮你写好了 while 循环来应对虚假唤醒:
// 你只需要传入锁,以及“在什么条件下需要继续睡眠”的闭包// 内部会自动循环检查,彻底杜绝虚假唤醒的隐患
started = cvar.wait_while(started, |state| !state.1).unwrap();
use core::time;
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
#[derive(PartialEq, Eq)]
enum which {
thread1,
thread2,
}
struct CondState {
count: i32,
turn: which,
}
pub fn run_cvar() {
let pair = Arc::new((
Mutex::new(CondState {
count: 0,
turn: which::thread1,
}),
Condvar::new(),
));
let pair1 = pair.clone();
let pair2 = pair.clone();
let handle1 = thread::spawn(move || printling1(pair1));
let handle2 = thread::spawn(move || printling2(pair2));
handle1.join();
handle2.join();
}
pub fn printling1(state: Arc<(Mutex<CondState>, Condvar)>) {
let (lock, cvar) = &*state;
loop {
let mut shared = lock.lock().unwrap();
shared = cvar.wait_while(shared, |state| state.count <20 && state.turn != which::thread1).unwrap();
shared.count >= {
;
}
(, shared.count);
shared.count += ;
shared.turn = which::thread2;
cvar.();
thread::(time::Duration::());
}
}
(state: Arc<(Mutex<CondState>, Condvar)>) {
(lock, cvar) = &*state;
{
= lock.().();
shared = cvar.(shared, |state| state.count < && state.turn != which::thread2).();
shared.count >= {
;
}
(, shared.count);
shared.count += ;
shared.turn = which::thread1;
cvar.();
thread::(time::Duration::());
}
}
test {
super::*;
thread_test {
() {
super::();
}
}
}