共享内存

共享内存

在消息传递之外,还存在一种广为人知的并发模型,那就是共享内存。其实如果不能共享内存,消息传递也是不能在不同的线程间传递消息,也谈不上在不同的线程间等待和通知了。共享内存是这一切得以发生的基础。如果查看源码,你会发现消息传递的内部实现就是借用了共享内存机制。相对于消息传递而言,共享内存会有更多的竞争,但是不用进行多次拷贝,在某些情况下,也需要考虑使用这种方式来处理。在 Rust 中,能共享内存的情况,主要体现在下面两个方面。

static

Rust 语言中也存在 static 变量,其生命周期是整个应用程序,并且在内存中某个固定地址处只存在一份实例。所有线程都能够访问到它。这种方式也是最简单和直接的共享方式。几乎大多数语言都存在这种机制。下面简单看一下 Rust 中多个线程访问 static 变量的用法:

use std::thread;

static VAR: i32 = 5;

fn main() {
    // 创建一个新线程
    let new_thread = thread::spawn(move|| {
        println!("static value in new thread: {}", VAR);
    });

    // 等待新线程先运行
    new_thread.join().unwrap();
    println!("static value in main thread: {}", VAR);
}

static value in new thread: 5
static value in main thread: 5

VAR 这个 static 变量在各线程中可以直接使用,非常方便。当然上面只是读取,那么要修改也是很简单的:

use std::thread;

static mut VAR: i32 = 5;

fn main() {
    // 创建一个新线程
    let new_thread = thread::spawn(move|| {
        unsafe {
            println!("static value in new thread: {}", VAR);
            VAR = VAR + 1;
        }
    });

    // 等待新线程先运行
    new_thread.join().unwrap();
    unsafe {
        println!("static value in main thread: {}", VAR);
    }
}

static value in new thread: 5
static value in main thread: 6

从结果来看 VAR 的值变了,从代码上来看,除了在 VAR 变量前面加了 mut 关键字外,更加明显的是在使用 VAR 的地方都添加了 unsafe 代码块。为什么?所有的线程都能访问 VAR,且它是可以被修改的,自然就是不安全的。上面的代码比较简单,同一时间只会有一个线程读写 VAR,不会有什么问题,所以用 unsafe 来标记就可以。如果是更多的线程,还是请使用接下来要介绍的同步机制来处理。

由于现代操作系统的设计,线程寄生于进程,可以共享进程的资源,如果要在各个线程中共享一个变量,那么除了上面的 static,还有就是把变量保存在堆上了。当然 Rust 也不例外,遵从这一设计。只是我们知道 Rust 在安全性上肯定又会做一些考量,从而在语言设计和使用上稍有不同。

use std::thread;
use std::sync::Arc;

fn main() {
    let var : Arc<i32> = Arc::new(5);
    let share_var = var.clone();

    // 创建一个新线程
    let new_thread = thread::spawn(move|| {
        println!("share value in new thread: {}, address: {:p}", share_var, &*share_var);
    });

    // 等待新建线程先执行
    new_thread.join().unwrap();
    println!("share value in main thread: {}, address: {:p}", var, &*var);
}

share value in new thread: 5, address: 0x2825070
share value in main thread: 5, address: 0x2825070

Box 创建的变量要想在多个线程中安全使用,我们还需要实现很多功能才行,需要是 Sync,而 Arc 正是利用 Box 来实现的一个通过引用计数来共享状态的包裹类。下面引用一段 Arc::new 的源码即可看出它是通过 Box 来实现的:

pub fn new(data: T) -> Arc<T> {
    // Start the weak pointer count as 1 which is the weak pointer that's
    // held by all the strong pointers (kinda), see std/rc.rs for more info
    let x: Box<_> = box ArcInner {
        strong: atomic::AtomicUsize::new(1),
        weak: atomic::AtomicUsize::new(1),
        data: data,
    };
    Arc { _ptr: unsafe { NonZero::new(Box::into_raw(x)) } }
}

通过上面的运行结果,我们也可以发现新建线程和主线程中打印的 address 是一样的,说明状态确实是在同一个内存地址处。

下一页