Arc
你还记得我们用Rc
来给一个变量一个以上的所有者。如果我们在线程中做同样的事情,我们需要一个 Arc
。Arc
的意思是 "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
把数据包起来,然后用一个Arc
把Mutex
包起来。
所以我们用一个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); }