Arc

你还记得我们用Rc来给一个变量一个以上的所有者。如果我们在线程中做同样的事情,我们需要一个 ArcArc的意思是 "atomic reference counter"(原子引用计数器)。原子的意思是它使用计算机的处理器,所以每次只写一次数据。这一点很重要,因为如果两个线程同时写入数据,你会得到错误的结果。例如,想象一下,如果你能在Rust中做到这一点。


#![allow(unused)]
fn main() {
// 🚧
let mut x = 10;

for i in 0..10 { // Thread 1
    x += 1
}
for i in 0..10 { // Thread 2
    x += 1
}
}

如果线程1和线程2一起启动,也许就会出现这种情况。

  • 线程1看到10,写下11,然后线程2看到11,写下12 然后线程2看到11,写入12。到目前为止没有问题。
  • 线程1看到12。同时,线程2看到12。线程一看到13,写下13 线程2也写了13 现在我们有13个,但应该是14个 Now we have 13, but it should be 14. 这是个大问题。

Arc使用处理器来确保这种情况不会发生,所以当你有线程时必须使用这种方法。不过不建议单线程上用Arc,因为Rc更快一些。

不过你不能只用一个Arc来改变数据。所以你用一个Mutex把数据包起来,然后用一个ArcMutex包起来。

所以我们用一个Mutex在一个Arc里面来改变一个数字的值。首先我们设置一个线程。

fn main() {

    let handle = std::thread::spawn(|| {
        println!("The thread is working!") // Just testing the thread
    });

    handle.join().unwrap(); // Make the thread wait here until it is done
    println!("Exiting the program");
}

到目前为止,这个只打印:

The thread is working!
Exiting the program

很好,现在让我们把它放在for的循环中,进行0..5

fn main() {

    let handle = std::thread::spawn(|| {
        for _ in 0..5 {
            println!("The thread is working!")
        }
    });

    handle.join().unwrap();
    println!("Exiting the program");
}

这也是可行的。我们得到以下结果:

The thread is working!
The thread is working!
The thread is working!
The thread is working!
The thread is working!
Exiting the program

现在我们再加一个线程。每个线程都会做同样的事情。你可以看到,这些线程是在同一时间工作的。有时会先打印Thread 1 is working!,但其他时候Thread 2 is working!先打印。这就是所谓的并发,也就是 "一起运行"的意思。

fn main() {

    let thread1 = std::thread::spawn(|| {
        for _ in 0..5 {
            println!("Thread 1 is working!")
        }
    });

    let thread2 = std::thread::spawn(|| {
        for _ in 0..5 {
            println!("Thread 2 is working!")
        }
    });

    thread1.join().unwrap();
    thread2.join().unwrap();
    println!("Exiting the program");
}

这将打印:

Thread 1 is working!
Thread 1 is working!
Thread 1 is working!
Thread 1 is working!
Thread 1 is working!
Thread 2 is working!
Thread 2 is working!
Thread 2 is working!
Thread 2 is working!
Thread 2 is working!
Exiting the program

现在我们要改变my_number的数值。现在它是一个i32。我们将把它改为 Arc<Mutex<i32>>:一个可以改变的 i32,由 Arc 保护。


#![allow(unused)]
fn main() {
// 🚧
let my_number = Arc::new(Mutex::new(0));
}

现在我们有了这个,我们可以克隆它。每个克隆可以进入不同的线程。我们有两个线程,所以我们将做两个克隆。


#![allow(unused)]
fn main() {
// 🚧
let my_number = Arc::new(Mutex::new(0));

let my_number1 = Arc::clone(&my_number); // This clone goes into Thread 1
let my_number2 = Arc::clone(&my_number); // This clone goes into Thread 2
}

现在,我们已经将安全克隆连接到my_number,我们可以将它们move到其他线程中,没有问题。

use std::sync::{Arc, Mutex};

fn main() {
    let my_number = Arc::new(Mutex::new(0));

    let my_number1 = Arc::clone(&my_number);
    let my_number2 = Arc::clone(&my_number);

    let thread1 = std::thread::spawn(move || { // Only the clone goes into Thread 1
        for _ in 0..10 {
            *my_number1.lock().unwrap() +=1; // Lock the Mutex, change the value
        }
    });

    let thread2 = std::thread::spawn(move || { // Only the clone goes into Thread 2
        for _ in 0..10 {
            *my_number2.lock().unwrap() += 1;
        }
    });

    thread1.join().unwrap();
    thread2.join().unwrap();
    println!("Value is: {:?}", my_number);
    println!("Exiting the program");
}

程序打印:

Value is: Mutex { data: 20 }
Exiting the program

所以这是一个成功的案例。

然后我们可以将两个线程连接在一起,形成一个for循环,并使代码更短。

我们需要保存句柄,这样我们就可以在循环外对每个线程调用.join()。如果我们在循环内这样做,它将等待第一个线程完成后再启动新的线程。

use std::sync::{Arc, Mutex};

fn main() {
    let my_number = Arc::new(Mutex::new(0));
    let mut handle_vec = vec![]; // JoinHandles will go in here

    for _ in 0..2 { // do this twice
        let my_number_clone = Arc::clone(&my_number); // Make the clone before starting the thread
        let handle = std::thread::spawn(move || { // Put the clone in
            for _ in 0..10 {
                *my_number_clone.lock().unwrap() += 1;
            }
        });
        handle_vec.push(handle); // save the handle so we can call join on it outside of the loop
                                 // If we don't push it in the vec, it will just die here
    }

    handle_vec.into_iter().for_each(|handle| handle.join().unwrap()); // call join on all handles
    println!("{:?}", my_number);
}

最后这个打印Mutex { data: 20 }

这看起来很复杂,但Arc<Mutex<SomeType>>>在Rust中使用的频率很高,所以它变得很自然。另外,你也可以随时写你的代码,让它更干净。这里是同样的代码,多了一条use语句和两个函数。这些函数并没有做任何新的事情,但是它们把一些代码从main()中移出。如果你很难读懂的话,可以尝试重写这样的代码。

use std::sync::{Arc, Mutex};
use std::thread::spawn; // Now we just write spawn

fn make_arc(number: i32) -> Arc<Mutex<i32>> { // Just a function to make a Mutex in an Arc
    Arc::new(Mutex::new(number))
}

fn new_clone(input: &Arc<Mutex<i32>>) -> Arc<Mutex<i32>> { // Just a function so we can write new_clone
    Arc::clone(&input)
}

// Now main() is easier to read
fn main() {
    let mut handle_vec = vec![]; // each handle will go in here
    let my_number = make_arc(0);

    for _ in 0..2 {
        let my_number_clone = new_clone(&my_number);
        let handle = spawn(move || {
            for _ in 0..10 {
                let mut value_inside = my_number_clone.lock().unwrap();
                *value_inside += 1;
            }
        });
        handle_vec.push(handle);    // the handle is done, so put it in the vector
    }

    handle_vec.into_iter().for_each(|handle| handle.join().unwrap()); // Make each one wait

    println!("{:?}", my_number);
}