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

為什么會有這樣的錯(cuò)誤?這就是Rust在內(nèi)存方面更加嚴(yán)謹(jǐn)?shù)脑颉?/p>
上面Rust的錯(cuò)誤信息中也給出了原因,總結(jié)起來主要有兩點(diǎn):
- 線程的生命周期:新創(chuàng)建的線程的生命周期有可能超出主函數(shù)
main的執(zhí)行范圍。當(dāng)main函數(shù)終止時(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);
});
這樣就可以正常運(yùn)行了。

不過,這樣,主線程中就無法使用變量msg了,比如在main函數(shù)的最后打印msg,會報(bào)錯(cuò),因?yàn)樗乃袡?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);
}
很遺憾,依然有錯(cuò)誤:

錯(cuò)誤的原因仍然是傳入線程中的變量引用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í)際數(shù)據(jù)(
msg)仍然由原始范圍擁有,并具有自己的生命周期約束
為了修復(fù)這個(gè)錯(cuò)誤,就要用到Rust中提供的并發(fā)原語Arc(一種自動引用計(jì)數(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修改之后,變量不僅可以在多個(gè)線程中共享,主線程中也可以使用。

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

10個(gè)線程,每個(gè)累加1000,所以最后結(jié)果是1000*10=10000。
4. 總結(jié)
從上面的例子可以看出,Rust的閉包捕獲規(guī)則最初可能感覺很嚴(yán)格,但它們在確保內(nèi)存安全和數(shù)據(jù)競爭自由方面至關(guān)重要。
總之,
如果需要在另一個(gè)線程中擁有數(shù)據(jù),考慮使用move;
如果需要跨線程共享數(shù)據(jù),考慮使用Arc;
如果需要跨線程共享和修改數(shù)據(jù),考慮使用Arc+Mutex;
到此這篇關(guān)于淺析Rust多線程中如何安全的使用變量的文章就介紹到這了,更多相關(guān)Rust多線程使用變量內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust 中的閉包之捕獲環(huán)境的匿名函數(shù)
這篇文章介紹了Rust編程語言中的閉包,包括閉包的定義、使用、捕獲環(huán)境中的變量、類型推斷與注解、與函數(shù)的比較以及實(shí)際應(yīng)用,閉包具有捕獲環(huán)境、類型推斷和高效性等特性,是Rust中一個(gè)非常強(qiáng)大的工具,感興趣的朋友一起看看吧2025-02-02
Rust 所有權(quán)(Ownership)的使用小結(jié)
Rust通過所有權(quán)系統(tǒng)管理內(nèi)存,確保安全且無運(yùn)行時(shí)性能損失,所有權(quán)系統(tǒng)的核心規(guī)則包括唯一所有者、所有權(quán)失效時(shí)值自動釋放和值在任何時(shí)刻只能有一個(gè)所有者,本教程詳細(xì)介紹了所有權(quán)的規(guī)則、棧與堆的區(qū)別以及所有權(quán)在變量賦值、函數(shù)傳參和返回值中的應(yīng)用2026-01-01
Rust?搭建一個(gè)小程序運(yùn)行環(huán)境的方法詳解
rust是一門比較新的編程語言,2015年5月15日,Rust編程語言核心團(tuán)隊(duì)正式宣布發(fā)布Rust?1.0版本,本文給大家介紹Rust?搭建一個(gè)小程序運(yùn)行環(huán)境,以iOS?為例介紹開發(fā)環(huán)境的準(zhǔn)備,感興趣的朋友跟隨小編一起看看吧2022-05-05
在Rust中要用Struct和Enum組織數(shù)據(jù)的原因解析
在Rust中,Struct和Enum是組織數(shù)據(jù)的核心工具,Struct用于將相關(guān)字段封裝為單一實(shí)體,便于管理和擴(kuò)展,Enum用于明確定義所有可能的狀態(tài),本文將通過具體示例,深入探討為什么在Rust中必須使用struct和enum來管理數(shù)據(jù),感興趣的朋友一起學(xué)習(xí)吧2025-02-02

