在线程间消息传递中使用的消息传递机制,在某种程度上,任何编程语言中的信道都类似于单所有权,因为一旦将一个值传送到信道中,将无法再使用这个值了。 共享内存类似于多所有权:多个线程可以同时访问相同的内存位置。
接下来便了解一下Rust中的并发原语。
互斥器
互斥器mutex一次只允许一个线程访问数据。线程需要先通过获取互斥器的锁来表明其希望访问数据。并且再使用完成后必须释放锁,这样其它线程才能获取锁。
正确的管理互斥器异常复杂,但是Rust中,得益于类型系统和所有权,在锁和解锁上不会出错。
Mutex的API
一个使用Mutex的简单示例如下:
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
}
println!("m = {:?}", m);
}
使用关联函数new来创建一个Mutex<T>,使用lock()方法获取锁,来访问互斥器中的数据。这个调用会阻塞当前线程,直到拥有锁。
如果另一个线程获取到了锁,但是panic了,那么其余线程的lock调用会失败,这里选择使用unwrap遇到这种请求时使线程panic。
Mutex<T>的lock调用返沪的使一个叫做MutexGuard的智能指针,离开作用域时自动释放锁,所以上面的例子中将操作共享内存的代码放入了一个新的作用域中。解决了锁释放时机的问题,避免了用户忘记释放锁。
线程间共享Mutex
启动10个线程,并在各个线程中对同一个计数器加1。
共享内存需要同步使用Mutex,多线程共享使用Arc
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
Refcell/Rc与Mutex/Arc
这两者是有类似的。都是一个内部可变性与多所有权的组合。只不过一个组合用于单线程,另一个组合用于多线程。
为了便于使用,这样理解:
- Rc是单线程的多所有权,如果希望其内容可变,使用Refcell提供内部可变性。
- Arc是多线程的多所有权,希望其内部可变,使用Mutex提供内部可变性。