线程间消息传递中使用的消息传递机制,在某种程度上,任何编程语言中的信道都类似于单所有权,因为一旦将一个值传送到信道中,将无法再使用这个值了。 共享内存类似于多所有权:多个线程可以同时访问相同的内存位置。

接下来便了解一下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提供内部可变性。