锁
锁
在多线程中共享资源,除了原子类型之外,还可以考虑用锁来实现。在操作之前必须先获得锁,一把锁同时只能给一个线程,这样能保证同一时间只有一个线程能操作共享资源,操作完成后,再释放锁给等待的其他线程。在 Rust 中 std::sync::Mutex 就是一种锁。下面我们用 Mutex 来实现一下上面的原子类型的例子:
use std::thread;
use std::sync::{Arc, Mutex};
fn main() {
let var : Arc<Mutex<u32>> = Arc::new(Mutex::new(5));
let share_var = var.clone();
// 创建一个新线程
let new_thread = thread::spawn(move|| {
let mut val = share_var.lock().unwrap();
println!("share value in new thread: {}", *val);
// 修改值
*val = 9;
});
// 等待新建线程先执行
new_thread.join().unwrap();
println!("share value in main thread: {}", *(var.lock().unwrap()));
}
share value in new thread: 5
share value in main thread: 9
结果都一样,看来用 Mutex 也能实现,但如果从效率上比较,原子类型会更胜一筹。暂且不论这点,我们从代码里面看到,虽然有 lock,但是并么有看到有类似于 unlock 的代码出现,并不是不需要释放锁,而是 Rust 为了提高安全性,已然在 val 销毁的时候,自动释放锁了。同时我们发现,为了修改共享的值,开发者必须要调用 lock 才行,这样就又解决了一个安全问题。不得不再次赞叹一下 Rust 在多线程方面的安全性做得真是太好了。如果是其他语言,我们要做到安全,必然得自己来实现这些。
为了保障锁使用的安全性问题,Rust 做了很多工作,但从效率来看还不如原子类型,那么锁是否就没有存在的价值了?显然事实不可能是这样的,既然存在,那必然有其价值。它能解决原子类型锁不能解决的那百分之十的问题。我们再来看一下之前的一个例子:
use std::sync::{Arc, Mutex, Condvar};
use std::thread;
fn main() {
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = pair.clone();
// 创建一个新线程
thread::spawn(move|| {
let &(ref lock, ref cvar) = &*pair2;
let mut started = lock.lock().unwrap();
*started = true;
cvar.notify_one();
println!("notify main thread");
});
// 等待新线程先运行
let &(ref lock, ref cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
println!("before wait");
started = cvar.wait(started).unwrap();
println!("after wait");
}
}
代码中的 Condvar 就是条件变量,它提供了 wait 方法可以主动让当前线程等待,同时提供了 notify_one 方法,让其他线程唤醒正在等待的线程。这样就能完美实现顺序控制了。看起来好像条件变量把事都做完了,要 Mutex 干嘛呢?为了防止多个线程同时执行条件变量的 wait 操作,因为条件变量本身也是需要被保护的,这就是锁能做,而原子类型做不到的地方。
在 Rust 中,Mutex 是一种独占锁,同一时间只有一个线程能持有这个锁。这种锁会导致所有线程串行起来,这样虽然保证了安全,但效率并不高。对于写少读多的情况来说,如果在没有写的情况下,都是读取,那么应该是可以并发执行的,为了达到这个目的,几乎所有的编程语言都提供了一种叫读写锁的机制。