鍍金池/ 教程/ Java/ 并發(fā)編程
標(biāo)準(zhǔn)輸入與輸出
消息傳遞
循環(huán)
注釋
Rust for Mac OS
幾種智能指針
Cell, RefCell
trait對(duì)象 (trait object)
rust web 開發(fā)
Unsafe、原始指針
Macro
迭代器
函數(shù)
Borrow, BorrowMut, ToOwned
快速上手
二叉樹
編輯器
測(cè)試與評(píng)測(cè)
Deref
安裝Rust
哈希表 HashMap
原生類型
17.錯(cuò)誤處理
VS Code 安裝配置
動(dòng)態(tài)數(shù)組Vec
模式匹配
操作符和格式化字符串
Rust for Linux
函數(shù)參數(shù)
Visual Studio
vim/GVim安裝配置
閉包作為參數(shù)和返回值
安全(Safety)
Cow
生命周期( Lifetime )
閉包的實(shí)現(xiàn)
所有權(quán)(Ownership)
Atom
將Rust編譯成庫(kù)
類型、運(yùn)算符和字符串
類型系統(tǒng)中的幾個(gè)常見 trait
特性
屬性和編譯器參數(shù)
Spacemacs
集合類型
Rust json處理
Heap & Stack
并行
標(biāo)準(zhǔn)庫(kù)示例
基本程序結(jié)構(gòu)
鏈表
trait 和 trait對(duì)象
前期準(zhǔn)備
代碼風(fēng)格
編譯器參數(shù)
基于語(yǔ)義化版本的項(xiàng)目版本聲明與管理
Rust 版本管理工具: rustup
引用&借用(References&Borrowing)
注釋與文檔
10.1 trait關(guān)鍵字
模式
調(diào)用ffi函數(shù)
unsafe
并發(fā),并行,多線程編程
AsRef 和 AsMut
Rust旅程
Rust for Windows
結(jié)構(gòu)體與枚舉
條件分支
附錄I-術(shù)語(yǔ)表
變量綁定與原生類型
Mutex 與 RwLock
泛型
裸指針
常用數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)
系統(tǒng)命令:調(diào)用grep
Into/From 及其在 String 和 &str 互轉(zhuǎn)上的應(yīng)用
共享內(nèi)存
Sublime
網(wǎng)絡(luò)模塊:W貓的回音
函數(shù)返回值
包和模塊
高階函數(shù)
函數(shù)與方法
match關(guān)鍵字
隊(duì)列
目錄操作:簡(jiǎn)單grep
語(yǔ)句和表達(dá)式
并發(fā)編程
閉包
測(cè)試
閉包的語(yǔ)法
同步
迭代器
String
Send 和 Sync
Rc 和 Arc
屬性
Emacs
優(yōu)先隊(duì)列
Prelude
cargo簡(jiǎn)介
控制流(control flow)
數(shù)組、動(dòng)態(tài)數(shù)組和字符串
FFI
模塊和包系統(tǒng)、Prelude
實(shí)戰(zhàn)篇
Rust 是一門系統(tǒng)級(jí)編程語(yǔ)言,被設(shè)計(jì)為保證內(nèi)存和線程安全,并防止段錯(cuò)誤。作為系統(tǒng)級(jí)編程語(yǔ)言,它的基本理念是 “零開銷抽象”。理
運(yùn)算符重載
Any和反射
rust數(shù)據(jù)庫(kù)操作
輸入輸出流
復(fù)合類型
性能測(cè)試

并發(fā)編程

并發(fā)是什么?引用Rob Pike的經(jīng)典描述:

并發(fā)是同一時(shí)間應(yīng)對(duì)多件事情的能力

其實(shí)在我們身邊就有很多并發(fā)的事情,比如一邊上課,一邊發(fā)短信;一邊給小孩喂奶,一邊看電視,只要你細(xì)心留意,就會(huì)發(fā)現(xiàn)許多類似的事。相應(yīng)地,在軟件的世界里,我們也會(huì)發(fā)現(xiàn)這樣的事,比如一邊寫博客,一邊聽音樂;一邊看網(wǎng)頁(yè),一邊下載軟件等等。顯而易見這樣會(huì)節(jié)約不少時(shí)間,干更多的事。然而一開始計(jì)算機(jī)系統(tǒng)并不能同時(shí)處理兩件事,這明顯滿足不了我們的需要,后來(lái)慢慢提出了多進(jìn)程,多線程的解決方案,再后來(lái),硬件也發(fā)展到了多核多CPU的地步。在硬件和系統(tǒng)底層對(duì)并發(fā)的支持也來(lái)越多,相應(yīng)地,各大編程語(yǔ)言也對(duì)并發(fā)處理提供了強(qiáng)力的支持,作為新興語(yǔ)言的Rust,自然也支持并發(fā)編程。那么本章就將引領(lǐng)大家一覽Rust并發(fā)編程的相關(guān)知識(shí),從線程開始,逐步嘗試進(jìn)行數(shù)據(jù)交互,同步協(xié)作,最后進(jìn)入到并行實(shí)現(xiàn),一步一步揭開Rust并發(fā)編程的神秘面紗。由于本書主要介紹的是Rust語(yǔ)言的使用,所以本章不會(huì)對(duì)并發(fā)編程相關(guān)理論知識(shí)進(jìn)行全面而深入地探討——要真那樣地話,一本書都不夠介紹的,而是更側(cè)重于介紹用Rust語(yǔ)言怎么實(shí)現(xiàn)基本的并發(fā)。

首先我們會(huì)介紹線程的使用,線程是基本的執(zhí)行單元,其重要性不言而喻,Rust程序就是由一堆線程組成的。在當(dāng)今多核多CPU已經(jīng)普及的情況下,各種大數(shù)據(jù)分析和并行計(jì)算又讓線程煥發(fā)出了更耀眼的光芒。如果對(duì)線程不甚了解,請(qǐng)先參閱操作系統(tǒng)相關(guān)的書籍,此處不過(guò)多介紹。然后介紹一些在解決并發(fā)問題時(shí),需要處理的數(shù)據(jù)傳遞和協(xié)作的實(shí)現(xiàn),比如消息傳遞,同步和共享內(nèi)存。最后簡(jiǎn)要介紹Rust中并行的實(shí)現(xiàn)。

24.1 線程創(chuàng)建與結(jié)束

相信線程對(duì)大家而言,一點(diǎn)也不陌生,在當(dāng)今多CPU多核已經(jīng)普及的情況下,大數(shù)據(jù)分析與并行計(jì)算都離不開它,幾乎所有的語(yǔ)言都支持它,所有的進(jìn)程都是由一個(gè)或多個(gè)線程所組成的。既然如此重要,接下來(lái)我們就先來(lái)看一下在Rust中如何創(chuàng)建一個(gè)線程,然后線程又是如何結(jié)束的。

Rust對(duì)于線程的支持,和C++11一樣,都是放在標(biāo)準(zhǔn)庫(kù)中來(lái)實(shí)現(xiàn)的,詳情請(qǐng)參見std::thread,好在Rust從一開始就這樣做了,不用像C++那樣等呀等。在語(yǔ)言層面支持后,開發(fā)者就不用那么苦兮兮地處理各平臺(tái)的移植問題。通過(guò)Rust的源碼可以看到,std::thread其實(shí)就是對(duì)不同平臺(tái)的線程操作的封裝,相關(guān)API的實(shí)現(xiàn)都是調(diào)用操作系統(tǒng)的API來(lái)實(shí)現(xiàn)的,從而提供了線程操作的統(tǒng)一接口。對(duì)于我而言,能夠這樣簡(jiǎn)單快捷地操作原生線程,身上的壓力一下輕了不少。

創(chuàng)建線程

首先,我們看一下在Rust中如何創(chuàng)建一個(gè)原生線程(native thread)。std::thread提供了兩種創(chuàng)建方式,都非常簡(jiǎn)單,第一種方式是通過(guò)spawn函數(shù)來(lái)創(chuàng)建,參見下面的示例代碼:

use std::thread;

fn main() {
    // 創(chuàng)建一個(gè)線程
    let new_thread = thread::spawn(move || {
        println!("I am a new thread.");
    });
    // 等待新建線程執(zhí)行完成
    new_thread.join().unwrap();
}

執(zhí)行上面這段代碼,將會(huì)看到下面的輸出結(jié)果:

I am a new thread.

就5行代碼,少得不能再少,最關(guān)鍵的當(dāng)然就是調(diào)用spawn函數(shù)的那行代碼。使用這個(gè)函數(shù),記得要先use std::thread。注意spawn函數(shù)需要一個(gè)函數(shù)作為參數(shù),且是FnOnce類型,如果已經(jīng)忘了這種類型的函數(shù),請(qǐng)學(xué)習(xí)或回顧一下函數(shù)和閉包章節(jié)。main函數(shù)最后一行代碼即使不要,也能創(chuàng)建線程(關(guān)于join函數(shù)的作用和使用在后續(xù)小節(jié)詳解,此處你只要知道它可以用來(lái)等待線程執(zhí)行完成即可),可以去掉或者注釋該行代碼試試。這樣的話,運(yùn)行結(jié)果可能沒有任何輸出,具體原因后面詳解。

接下來(lái)我們使用第二種方式創(chuàng)建線程,它比第一種方式稍微復(fù)雜一點(diǎn),因?yàn)楣δ軓?qiáng)大一點(diǎn),可以在創(chuàng)建之前設(shè)置線程的名稱和堆棧大小,參見下面的代碼:

use std::thread;

fn main() {
    // 創(chuàng)建一個(gè)線程,線程名稱為 thread1, 堆棧大小為4k
    let new_thread_result = thread::Builder::new()
                            .name("thread1".to_string())
                            .stack_size(4*1024*1024).spawn(move || {
        println!("I am thread1.");
    });
    // 等待新創(chuàng)建的線程執(zhí)行完成
    new_thread_result.unwrap().join().unwrap();
}

執(zhí)行上面這段代碼,將會(huì)看到下面的輸出結(jié)果:

I am thread1.

通過(guò)和第一種方式的實(shí)現(xiàn)代碼比較可以發(fā)現(xiàn),這種方式借助了一個(gè)Builder類來(lái)設(shè)置線程名稱和堆棧大小,除此之外,Builderspawn函數(shù)的返回值是一個(gè)Result,在正式的代碼編寫中,可不能像上面這樣直接unwrap.join,應(yīng)該判定一下。后面也會(huì)有很多類似的演示代碼,為了簡(jiǎn)單說(shuō)明不會(huì)做的很嚴(yán)謹(jǐn)。

以上就是Rust創(chuàng)建原生線程的兩種不同方式,示例代碼有點(diǎn)然并卵的意味,但是你可以稍加修改,就可以變得更加有用,試試吧。

線程結(jié)束

此時(shí),我們已經(jīng)知道如何創(chuàng)建一個(gè)新線程了,創(chuàng)建后,不管你見或者不見,它就在那里,那么它什么時(shí)候才會(huì)消亡呢?自生自滅,亦或者被干掉?如果接觸過(guò)一些系統(tǒng)編程,應(yīng)該知道有些操作系統(tǒng)提供了粗暴地干掉線程的接口,看它不爽,直接干掉,完全可以不理會(huì)新建線程的感受。是否感覺很爽,但是Rust不會(huì)再讓這樣爽了,因?yàn)?code>std::thread并沒有提供這樣的接口,為什么呢?如果深入接觸過(guò)并發(fā)編程或多線程編程,就會(huì)知道強(qiáng)制終止一個(gè)運(yùn)行中的線程,會(huì)出現(xiàn)諸多問題。比如資源沒有釋放,引起狀態(tài)混亂,結(jié)果不可預(yù)期。強(qiáng)制干掉那一刻,貌似很爽地解決問題了,然而可能后患無(wú)窮。Rust語(yǔ)言的一大特性就是安全,是絕對(duì)不允許這樣不負(fù)責(zé)任的做法的。即使在其他語(yǔ)言提供了類似的接口,也不應(yīng)該濫用。

那么在Rust中,新建的線程就只能讓它自身自滅了嗎?其實(shí)也有兩種方式,首先介紹大家都知道的自生自滅的方式,線程執(zhí)行體執(zhí)行完成,線程就結(jié)束了。比如上面創(chuàng)建線程的第一種方式,代碼執(zhí)行完println!("I am a new thread.");就結(jié)束了。 如果像下面這樣:

use std::thread;

fn main() {
    // 創(chuàng)建一個(gè)線程
    let new_thread = thread::spawn(move || {
        loop {
            println!("I am a new thread.");
        }
    });
    // 等待新創(chuàng)建的線程執(zhí)行完成
    new_thread.join().unwrap();
}

線程就永遠(yuǎn)都不會(huì)結(jié)束,如果你用的還是古董電腦,運(yùn)行上面的代碼之前,請(qǐng)做好心理準(zhǔn)備。在實(shí)際代碼中,要時(shí)刻警惕該情況的出現(xiàn)(單核情況下,CPU占用率會(huì)飆升到100%),除非你是故意為之。

線程結(jié)束的另一種方式就是,線程所在進(jìn)程結(jié)束了。我們把上面這個(gè)例子稍作修改:

use std::thread;

fn main() {
    // 創(chuàng)建一個(gè)線程
    thread::spawn(move || {
        loop {
            println!("I am a new thread.");
        }
    });

    // 不等待新創(chuàng)建的線程執(zhí)行完成
    // new_thread.join().unwrap();
}

同上面的代碼相比,唯一的差別在于main函數(shù)的最后一行代碼被注釋了,這樣主線程就不用等待新建線程了,在創(chuàng)建線程之后就執(zhí)行完了,其所在進(jìn)程也就結(jié)束了,從而新建的線程也就結(jié)束了。此處,你可能有疑問:為什么一定是進(jìn)程結(jié)束導(dǎo)致新建線程結(jié)束?也可能是創(chuàng)建新線程的主線程結(jié)束而導(dǎo)致的?事實(shí)到底如何,我們不妨驗(yàn)證一下:

use std::thread;

fn main() {
    // 創(chuàng)建一個(gè)線程
    let new_thread = thread::spawn(move || {
        // 再創(chuàng)建一個(gè)線程
        thread::spawn(move || {
            loop {
                println!("I am a new thread.");
            }
        })
    });

    // 等待新創(chuàng)建的線程執(zhí)行完成
    new_thread.join().unwrap();
    println!("Child thread is finish!");

    // 睡眠一段時(shí)間,看子線程創(chuàng)建的子線程是否還在運(yùn)行
    thread::sleep_ms(100);
}

這次我們?cè)谛陆ň€程中還創(chuàng)建了一個(gè)線程,從而第一個(gè)新建線程是父線程,主線程在等待該父線程結(jié)束后,主動(dòng)睡眠一段時(shí)間。這樣做有兩個(gè)目的,一是確保整個(gè)程序不會(huì)馬上結(jié)束;二是如果子線程還存在,應(yīng)該會(huì)獲得執(zhí)行機(jī)會(huì),以此來(lái)檢驗(yàn)子線程是否還在運(yùn)行,下面是輸出結(jié)果:

Child thread is finish!
I am a new thread.
I am a new thread.
......

結(jié)果表明,在父線程結(jié)束后,其創(chuàng)建的子線程還活著,這并不會(huì)因?yàn)楦妇€程結(jié)束而結(jié)束。這個(gè)還是比較符合自然規(guī)律的,要不然真會(huì)斷子絕孫,人類滅絕。所以導(dǎo)致線程結(jié)束的第二種方式,是結(jié)束其所在進(jìn)程。到此為止,我們已經(jīng)把線程的創(chuàng)建和結(jié)束都介紹完了,那么接下來(lái)我們會(huì)介紹一些更有趣的東西。但是在此之前,請(qǐng)先考慮一下下面的練習(xí)題。

練習(xí)題:

有一組學(xué)生的成績(jī),我們需要對(duì)它們?cè)u(píng)分,90分及以上是A,80分及以上是B,70分及以上是C,60分及以上為D,60分以下為E。現(xiàn)在要求用Rust語(yǔ)言編寫一個(gè)程序來(lái)評(píng)分,且評(píng)分由新建的線程來(lái)做,最終輸出每個(gè)學(xué)生的學(xué)號(hào),成績(jī),評(píng)分。學(xué)生成績(jī)單隨機(jī)產(chǎn)生,學(xué)生人數(shù)100位,成績(jī)范圍為[0,100],學(xué)號(hào)依次從1開始,直到100。

上一篇:安裝Rust下一篇:迭代器