1. 概述

“不要用同享的内存来通讯,要用通讯来同享内存”。实际上,在上节中咱们就是运用通讯的办法来完成并发的,在本节咱们要运用同享内存的办法来完成并发。

rust支持通过同享状态来完成并发。channel类似于单一切权,一旦将值的一切权转移至channel,就无法运用它了。而同享内存的并发办法类似多一切权,多个线程能够同时拜访一块内存。

在rust里运用Mutex来每次只允许一个线程来拜访数据,是mutual exclusion(互斥锁)的简写。在同一时刻,Mutex只允许一个线程来拜访某些数据。想要拜访数据,线程有必要首先获取互斥锁(lock)。lock数据结构是mutex的一部分,它能跟踪谁对数据拥有独占拜访权。mutex通常被描绘为:通过锁定系统来维护它所持有的数据。

2. Mutext的两条规矩

  • 在运用数据之前,有必要测验获取锁(lock)。
  • 运用完mutex所维护的数据,有必要对数据进行解锁,以便其他线程能够获取锁。

3. Mutex的API

通过Mutex::new(数据)来创建Mutex<T>Mutex<T>是一个智能指针,在拜访数据之前,通过lock办法来获取锁。这个办法会堵塞当时线程的履行,回来的是MutexGuard(智能指针,完成了Deref和Drop),但lock办法可能会失利。

如下示例代码:

use std::sync::Mutex;
fn main() {
    let m = Mutex::new(5);
    {
        // 获取数据的可变引用
        let mut num = m.lock().unwrap();
        // 变更数据
        *num = 6;
    }
    // 因为MutexGuard完成了 Drop trait,当效果域走完,m被主动解锁
    println("m = {"?"}", m);
}

4. 多线程同享Mutex

在rust里运用Arc<T>来进行原子引用计数,Arc<T>Rc<T>类似,它能够用于并发场景。A是atomic的简称,即原子的。这时分你能够有一个问题,为什么一切的基础类型都不是原子的,为什么标准库类型不默许运用Arc<T>?因为需求支付功能价值。Arc<T>Rc<T>的API是相同的。

如下示例代码:

use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
    // 创建一个数据
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];
    // 创建10个线程去修改数据,再把回来的10个JoinHandle放到handles中
    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());
}

终究运转结果为10

5. RefCell/Rc和Mutex/Arc

Mutext<T>提供了内部可变性,和Cell宗族相同。咱们能够运用RefCell<T>改动Rc<T>里边的内容,同样能够运用Mutex<T>来改动Arc<T>里边的内容。

但要注意的是,运用Rc<T>可能会形成循环引用,形成内存走漏的危险;而运用Mutex<T>也有死锁的危险。所谓的死锁,就是当某个操作需求同时锁住两个资源,两个线程别离持有其中一个锁,并相互请求别的一个锁的时分,这两个线程就会陷入无穷无尽的等候。