淺析Rust多線程中如何安全的使用變量
在Rust語言中,一個既引人入勝又可能帶來挑戰(zhàn)的特性是閉包如何從其所在環(huán)境中捕獲變量,尤其是在涉及多線程編程的情境下。
如果嘗試在不使用move關(guān)鍵字的情況下創(chuàng)建新線程并傳遞數(shù)據(jù)至閉包內(nèi),編譯器將很可能返回一系列與生命周期、借用規(guī)則及所有權(quán)相關(guān)的復(fù)雜錯誤信息。
不過,這種機制雖然增加了學(xué)習(xí)曲線,但也確保了內(nèi)存安全與并發(fā)執(zhí)行中的數(shù)據(jù)一致性。
本文我們將探討如何在線程的閉包中安全的使用變量,包括共享變量和修改變量。
1. 向線程傳遞變量
首先,我們構(gòu)造一個簡單的示例,在線程中正常使用一個外部的變量,看看Rust中能否正常編譯運行。
use std::thread;
fn main() {
let msg = String::from("Hello World!");
let handle = thread::spawn(|| {
// msg 是主線中定義的變量
println!("{}", msg);
});
handle.join().unwrap();
}
例子非常簡單,看著寫法也沒什么問題,在其他編程語言中類似的寫法是沒有問題的。
但是,使用cargo run運行時,卻有如下的錯誤:

為什么會有這樣的錯誤?這就是Rust在內(nèi)存方面更加嚴謹?shù)脑颉?/p>
上面Rust的錯誤信息中也給出了原因,總結(jié)起來主要有兩點:
- 線程的生命周期:新創(chuàng)建的線程的生命周期有可能超出主函數(shù)
main的執(zhí)行范圍。當main函數(shù)終止時,與之相關(guān)的局部變量(也就是msg)將超出作用域。 - 不符合借用規(guī)則:在
Rust中,引用的生命周期不會超過其所指向數(shù)據(jù)的生命周期,以避免出現(xiàn)懸空引用。如果main提前結(jié)束,那么線程中的msg將成為懸空引用。
修復(fù)的方法很簡單,使用move關(guān)鍵字,將變量的所有權(quán)轉(zhuǎn)移到線程中就可以了。
let handle = thread::spawn(move || {
// msg 是主線中定義的變量
println!("{}", msg);
});
這樣就可以正常運行了。

不過,這樣,主線程中就無法使用變量msg了,比如在main函數(shù)的最后打印msg,會報錯,因為它的所有權(quán)已經(jīng)轉(zhuǎn)移到線程中了。
2. 多線程共享變量引用
如果我們只把變量的引用轉(zhuǎn)移給線程,是不是可以在主線程main中繼續(xù)使用變量msg呢?
use std::thread;
fn main() {
let msg = String::from("Hello World!");
let msg_ref = &msg;
let handle = {
thread::spawn(move || {
// msg 是主線中定義的變量
println!("{}", msg_ref);
})
};
handle.join().unwrap();
println!("msg in main : {}", msg_ref);
}
很遺憾,依然有錯誤:

錯誤的原因仍然是傳入線程中的變量引用msg_ref生命周期的不夠長。
雖然我們使用了move,將msg_ref轉(zhuǎn)移到線程中,但main中仍然擁有底層的數(shù)據(jù)msg,
一旦main函數(shù)結(jié)束(或者數(shù)據(jù)在線程完成之前超出范圍),該引用(msg_ref)指向數(shù)據(jù)將失去有效的內(nèi)存,成為懸空引用。
總的來說就是:
- 移動引用并不移動原始數(shù)據(jù)-只轉(zhuǎn)移引用本身的所有權(quán)
- 實際數(shù)據(jù)(
msg)仍然由原始范圍擁有,并具有自己的生命周期約束
為了修復(fù)這個錯誤,就要用到Rust中提供的并發(fā)原語Arc(一種自動引用計數(shù)的智能指針)。
先看看使用Arc修改后的例子。
use std::sync::Arc;
use std::thread;
fn main() {
let msg = String::from("Hello World!");
// 通過Arc來創(chuàng)建變量的引用
let msg_ref = Arc::new(msg);
// 線程1
let handle_1 = {
// move 之前,先使用Arc clone 變量
let msg_thread = Arc::clone(&msg_ref);
thread::spawn(move || {
println!("Thread 1: {}", msg_thread);
})
};
// 線程2
let handle_2 = {
let msg_thread = Arc::clone(&msg_ref);
thread::spawn(move || {
println!("Thread 2: {}", msg_thread);
})
};
handle_1.join().unwrap();
handle_2.join().unwrap();
// 主線程中依然可以使用變量
println!("msg in main : {}", msg_ref);
}
使用Arc修改之后,變量不僅可以在多個線程中共享,主線程中也可以使用。

3. 多線程中修改變量
上面的示例是在多個線程中共享變量,如果想要修改變量的話,那么就會出現(xiàn)數(shù)據(jù)競爭的情況。
這時,就要用到Rust的另一個并發(fā)原語Mutex。
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// 創(chuàng)建一個被Mutex保護的共享數(shù)據(jù),這里是一個i32類型的數(shù)字
let shared_number = Arc::new(Mutex::new(0));
// 定義一個線程向量,用于存儲創(chuàng)建的線程
let mut threads = Vec::new();
// 創(chuàng)建10個線程,每個線程對共享數(shù)據(jù)進行1000次遞增操作
for _ in 0..10 {
// 克隆Arc,使得每個線程都擁有一個指向共享數(shù)據(jù)的引用
let num_clone = Arc::clone(&shared_number);
let handle = thread::spawn(move || {
// 嘗試獲取Mutex的鎖,這是一個阻塞操作,如果鎖不可用,線程會等待
let mut num = num_clone.lock().unwrap();
for _ in 0..1000 {
*num += 1;
}
});
threads.push(handle);
}
// 等待所有線程完成操作
for handle in threads {
handle.join().unwrap();
}
// 獲取最終的共享數(shù)據(jù)值并打印
let final_num = shared_number.lock().unwrap();
println!("最終10個線程的累加結(jié)果: {}", final_num);
}
在這個示例中:
- 首先創(chuàng)建了一個
Arc<Mutex<i32>>類型的共享數(shù)據(jù),Arc用于在多個線程間共享Mutex,Mutex用于保護內(nèi)部的i32數(shù)據(jù)。 - 循環(huán)創(chuàng)建
10個線程,每個線程都克隆了Arc并嘗試獲取Mutex的鎖。一旦獲取到鎖,線程就可以安全地對共享數(shù)據(jù)進行遞增操作。 - 主線程使用
join方法等待所有子線程完成操作。 - 最后,主線程獲取并打印共享數(shù)據(jù)的最終值。由于Mutex的保護,多個線程對共享數(shù)據(jù)的操作不會產(chǎn)生數(shù)據(jù)競爭,保證了數(shù)據(jù)的一致性。
運行結(jié)果:

10個線程,每個累加1000,所以最后結(jié)果是1000*10=10000。
4. 總結(jié)
從上面的例子可以看出,Rust的閉包捕獲規(guī)則最初可能感覺很嚴格,但它們在確保內(nèi)存安全和數(shù)據(jù)競爭自由方面至關(guān)重要。
總之,
如果需要在另一個線程中擁有數(shù)據(jù),考慮使用move;
如果需要跨線程共享數(shù)據(jù),考慮使用Arc;
如果需要跨線程共享和修改數(shù)據(jù),考慮使用Arc+Mutex;
到此這篇關(guān)于淺析Rust多線程中如何安全的使用變量的文章就介紹到這了,更多相關(guān)Rust多線程使用變量內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何使用VSCode配置Rust開發(fā)環(huán)境(Rust新手教程)
這篇文章主要介紹了如何使用VSCode配置Rust開發(fā)環(huán)境(Rust新手教程),本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07
Rust-使用dotenvy加載和使用環(huán)境變量的過程詳解
系統(tǒng)的開發(fā),測試和部署離不開環(huán)境變量,今天分享在Rust的系統(tǒng)開發(fā)中,使用dotenvy來讀取和使用環(huán)境變量,感興趣的朋友跟隨小編一起看看吧2023-11-11

