Rust如何使用線程同時(shí)運(yùn)行代碼
一、Rust 的線程模型
Rust 標(biāo)準(zhǔn)庫使用的是 1:1 的線程模型,即每一個語言層的線程都對應(yīng)一個操作系統(tǒng)線程。Rust 中通過標(biāo)準(zhǔn)庫提供的 std::thread
模塊來創(chuàng)建、管理線程。
當(dāng)然,也有一些第三方庫會采用不同的線程模型,或者利用異步(async)機(jī)制來實(shí)現(xiàn)并發(fā)(比如 Rust 的 async/await 機(jī)制),在面對具體需求時(shí)可以根據(jù)實(shí)際情況做選擇。
二、創(chuàng)建線程:thread::spawn
要在 Rust 中創(chuàng)建一個新線程,可以使用 thread::spawn
函數(shù),并向它傳遞一個閉包(closure)。閉包中包含需要在線程中執(zhí)行的代碼。
例如:
use std::thread; use std::time::Duration; fn main() { // 使用 thread::spawn 創(chuàng)建新的線程 thread::spawn(|| { for i in 1..10 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } }); // 主線程也執(zhí)行一些操作 for i in 1..5 { println!("hi number {} from the main thread!", i); thread::sleep(Duration::from_millis(1)); } }
在上面的例子里,我們在子線程中打印數(shù)字,同時(shí)在主線程中也打印數(shù)字。由于操作系統(tǒng)會對線程進(jìn)行調(diào)度,輸出的順序無法完全預(yù)測。可能主線程先打印,也可能子線程先打印,或者兩者交錯執(zhí)行。
需要注意的是,當(dāng)主線程結(jié)束時(shí),所有通過 spawn
創(chuàng)建的子線程會被強(qiáng)制終止,即使子線程還沒有執(zhí)行完。
三、等待線程完成:JoinHandle 與 join
如果希望確保子線程的代碼一定會執(zhí)行完,那么就需要在主線程結(jié)束前等待子線程。thread::spawn
的返回值是一個 JoinHandle
,可以用它來調(diào)用 join
方法,阻塞(block)當(dāng)前線程,直到對應(yīng)的子線程執(zhí)行完成。
use std::thread; use std::time::Duration; fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } }); // 如果先做主線程自己的工作,再等待子線程 for i in 1..5 { println!("hi number {} from the main thread!", i); thread::sleep(Duration::from_millis(1)); } // 調(diào)用 join,阻塞主線程,直到子線程結(jié)束 handle.join().unwrap(); }
當(dāng)我們在主線程中調(diào)用 handle.join()
時(shí),主線程會暫停執(zhí)行,直到子線程完成工作。這樣就能確保在程序退出前,所有線程都能順利完成執(zhí)行。
如果把 join
放在主線程的循環(huán)之前,那么主線程會先等待子線程結(jié)束,才會進(jìn)行自身的打印操作——這樣就不會再看到主線程與子線程的輸出交錯了。
四、move 閉包與線程
多線程編程中常常需要在線程間傳遞數(shù)據(jù)或訪問主線程中的變量。
在 Rust 中,如果一個閉包想要捕獲外部變量,就要考慮該變量的所有權(quán)或引用生命周期問題。
4.1.問題場景
如下示例所示,如果我們在主線程中創(chuàng)建一個向量 v
,然后在子線程中直接打印這個向量,就會出錯:
use std::thread; fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(|| { println!("Here's a vector: {:?}", v); }); // ... handle.join().unwrap(); }
編譯時(shí),Rust 會提示閉包捕獲的是對 v
的引用,但無法保證在子線程運(yùn)行時(shí) v
依舊有效:主線程可能在子線程使用 v
之前就結(jié)束了,讓 v
不再有效,從而導(dǎo)致潛在的懸垂引用(dangling reference)。
4.2.使用 move 關(guān)鍵字
為了解決這個問題,需要在閉包前面加上 move
關(guān)鍵字,這樣可以把閉包中用到的外部數(shù)據(jù)移動到閉包的所有權(quán)中。
use std::thread; fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(move || { println!("Here's a vector: {:?}", v); }); handle.join().unwrap(); }
在這里,move
會把 v
的所有權(quán)從主線程轉(zhuǎn)移到子線程,從而保證子線程在使用 v
時(shí)不會遇到生命周期問題。不過需要注意的是,這樣一來,主線程就不能再使用 v
了,因?yàn)樗袡?quán)已經(jīng)被移動出去了。
4.3.不能與 drop 共用所有權(quán)
如果嘗試同時(shí)在主線程中顯式調(diào)用 drop(v)
并且在子線程中使用 v
,無論有沒有用 move
,都行不通。Rust 的所有權(quán)規(guī)則會保證同一份數(shù)據(jù)不會被多次釋放或引用到失效的數(shù)據(jù)。所以,在設(shè)計(jì)多線程邏輯時(shí),需要明確劃分?jǐn)?shù)據(jù)的所有權(quán)與生命周期,以避免死鎖、競態(tài)條件或懸垂引用等問題。
五、小結(jié)
- Rust 標(biāo)準(zhǔn)庫中的線程模型:Rust 使用一對一(1:1)模型,每個語言線程對應(yīng)一個系統(tǒng)線程。
- 創(chuàng)建線程:使用
thread::spawn
來創(chuàng)建子線程,傳入一個閉包作為要執(zhí)行的代碼。 - 線程同步:通過返回的
JoinHandle
調(diào)用join
,可以阻塞主線程并等待子線程完成執(zhí)行。 - 所有權(quán)與生命周期:使用
move
關(guān)鍵字將閉包所需的變量從主線程移動到子線程,從而避免引用沖突或無效引用。 - 小心共享數(shù)據(jù):當(dāng)多個線程需要同時(shí)訪問或修改同一份數(shù)據(jù)時(shí),需要使用安全的并發(fā)原語(例如
Mutex
、RwLock
、Arc
等),否則會出現(xiàn)競態(tài)條件。
在 Rust 中編寫并發(fā)程序時(shí),我們需要充分利用所有權(quán)與借用檢查器提供的安全保障,同時(shí)對多線程邏輯進(jìn)行精心設(shè)計(jì)。盡管多線程編程能帶來性能上的提升,但也應(yīng)關(guān)注潛在的風(fēng)險(xiǎn),并通過 Rust 的工具鏈和語言特性來盡量減少錯誤,寫出更安全、更可靠的并發(fā)應(yīng)用。
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Rust使用Sled添加高性能嵌入式數(shù)據(jù)庫
這篇文章主要為大家詳細(xì)介紹了如何在Rust項(xiàng)目中使用Sled庫,一個為Rust生態(tài)設(shè)計(jì)的現(xiàn)代、高性能嵌入式數(shù)據(jù)庫,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03解讀Rust的Rc<T>:實(shí)現(xiàn)多所有權(quán)的智能指針方式
Rc<T> 是 Rust 中用于多所有權(quán)的引用計(jì)數(shù)類型,通過增加引用計(jì)數(shù)來管理共享數(shù)據(jù),只有當(dāng)最后一個引用離開作用域時(shí),數(shù)據(jù)才會被釋放,Rc<T> 適用于單線程環(huán)境,并且只允許不可變共享數(shù)據(jù);需要可變共享時(shí)應(yīng)考慮使用 RefCell<T> 或其他解決方案2025-02-02Rust?HashMap詳解及單詞統(tǒng)計(jì)示例用法詳解
HashMap在Rust中是一個強(qiáng)大的工具,通過合理使用可以簡化很多與鍵值對相關(guān)的問題,在實(shí)際開發(fā)中,我們可以充分利用其特性,提高代碼的效率和可讀性,本文將深入介紹HashMap的特性,以及通過一個單詞統(tǒng)計(jì)的例子展示其用法,感興趣的朋友一起看看吧2024-02-02