互斥锁的加锁顺序是不被保证的。第一个线程有可能100%获得锁,第二个线程却0%获得锁。
线程的调度由操作系统控制,以下情况是完全可能的:
1. 操作系统给予第一个线程 CPU 时间,它获取到锁。
2. 操作系统给予第二个线程 CPU 时间,但锁已被占用,因此它进入睡眠状态。
3. 第一个线程释放了锁,但仍被操作系统允许运行。它继续循环并重新获取锁。
4. 因为锁仍被占用,另一个线程无法继续执行。
如果你给第二个线程更多的时间来获取锁,就会看到预期的“乒乓”模式,但这并不是保证的(糟糕的操作系统可能决定永远不给某些线程 CPU 时间)。
use std::sync::{Arc, Mutex};
use std::{thread, time};
fn main() {
let data1 = Arc::new(Mutex::new(1));
let data2 = data1.clone();
let ten_millis = time::Duration::from_millis(10);
let a = thread::spawn(move || loop {
let mut data = data1.lock().unwrap();
*data += 1;
if *data > 10 {
break;
}
drop(data);
thread::sleep(ten_millis);
println!("ping ");
});
let b = thread::spawn(move || loop {
let mut data = data2.lock().unwrap();
*data += 1;
if *data > 10 {
break;
}
drop(data);
thread::sleep(ten_millis);
println!("pong ");
});
a.join().unwrap();
b.join().unwrap();
}
你可以通过调整睡眠时间来验证这一点。睡眠时间越短,乒乓交替就会更加不规则,当时间低至10毫秒时,你可能会看到ping-ping-pong等结果。
基于时间的解决方案本质上是设计不良的。你可以通过改进算法来保证“ping”后面跟着“pong”。例如,你可以在奇数上打印“ping”,在偶数上打印“pong”。
use std::sync::{Arc, Mutex};
use std::{thread, time};
const MAX_ITER: i32 = 10;
fn main() {
let data1 = Arc::new(Mutex::new(1));
let data2 = data1.clone();
let ten_millis = time::Duration::from_millis(10);
let a = thread::spawn(move || 'outer: loop {
loop {
thread::sleep(ten_millis);
let mut data = data1.lock().unwrap();
if *data > MAX_ITER {
break 'outer;
}
if *data & 1 == 1 {
*data += 1;
println!("ping ");
break;
}
}
});
let b = thread::spawn(move || 'outer: loop {
loop {
thread::sleep(ten_millis);
let mut data = data2.lock().unwrap();
if *data > MAX_ITER {
break 'outer;
}
if *data & 1 == 0 {
*data += 1;
println!("pong ");
break;
}
}
});
a.join().unwrap();
b.join().unwrap();
}
这并不是最好的实现方式,但我试图在尽可能少地修改原始代码的情况下进行。
你也可以考虑使用 Condvar
进行实现,这是一种更好的解决方案,在我的看法中,它避免了在互斥锁上进行忙等待的问题(备注:也删除了代码重复):
use std::sync::{Arc, Mutex, Condvar};
use std::thread;
const MAX_ITER: i32 = 10;
fn main() {
let cv1 = Arc::new((Condvar::new(), Mutex::new(1)));
let cv2 = cv1.clone();
let a = thread::spawn(ping_pong_task("ping", cv1, |x| x & 1 == 1));
let b = thread::spawn(ping_pong_task("pong", cv2, |x| x & 1 == 0));
a.join().unwrap();
b.join().unwrap();
}
fn ping_pong_task<S: Into<String>>(
msg: S,
cv: Arc<(Condvar, Mutex<i32>)>,
check: impl Fn(i32) -> bool) -> impl Fn()
{
let message = msg.into();
move || {
let (condvar, mutex) = &*cv;
let mut value = mutex.lock().unwrap();
loop {
if check(*value) {
println!("{} ", message);
*value += 1;
condvar.notify_all();
}
if *value > MAX_ITER {
break;
}
value = condvar.wait(value).unwrap();
}
}
}