線程、進(jìn)程、協(xié)程

操作系統(tǒng)和進(jìn)程

要理解這幾個名詞,我想從頭說起。在你將計(jì)算機(jī)通電那一刻開始,如果所有必要的硬件都工作正常,操作系統(tǒng)就會被引導(dǎo)起來。

操作系統(tǒng)(英語:operating system,縮寫:OS)是管理計(jì)算機(jī)硬件與軟件資源的計(jì)算機(jī)程序,同時也是計(jì)算機(jī)系統(tǒng)的內(nèi)核與基石。操作系統(tǒng)需要處理如管理與配置內(nèi)存、決定系統(tǒng)資源供需的優(yōu)先次序、控制輸入與輸出設(shè)備、操作網(wǎng)絡(luò)與管理文件系統(tǒng)等基本事務(wù)。操作系統(tǒng)也提供一個讓用戶與系統(tǒng)交互的操作界面。
操作系統(tǒng) - 維基百科,自由的百科全書

我在讀碩士的時候,教授操作系統(tǒng)的教授用了一個到目前為止我認(rèn)為最為貼切的一個單詞來描述操作系統(tǒng):Abstraction。大多數(shù)情況下,操作系統(tǒng)會分為內(nèi)核層(kernel)和用戶層(user)。前者是真正對硬件實(shí)現(xiàn)封裝的部分,同時它也會向用戶層提供一些API用來訪問硬件資源,即系統(tǒng)調(diào)用(system call),至于用戶層是如何管理這些資源的,就取決于各自的實(shí)現(xiàn)了。一般來說,兩個層是互相隔離的,只能通過system call。注意,這里“一般來說”很重要,因?yàn)橛∠笾?,我記得有一種OS是允許用戶層訪問內(nèi)核空間的,或者說是臨時允許,具體是哪個我沒搜到。

這樣看來,對于用戶來說,操作系統(tǒng)就是對硬件的一種抽象,使得用戶層可以用一些名字容易理解的函數(shù)(就是那種有時會包含匯編的C代碼實(shí)現(xiàn)的函數(shù))來訪問硬件資源。用戶層也會有不同類型的模塊,這些模塊可能會因?yàn)楣δ懿煌植煌举|(zhì)上都算進(jìn)程。這些模塊,或者說這些進(jìn)程一般都會實(shí)現(xiàn)某一種功能,用戶也可以自定義程序,這就是我們大多數(shù)時候在做的事情。這里有個概念比較容易混淆,雖然大多數(shù)時候一個程序?qū)?yīng)了一個進(jìn)程,但是這并不意味著一個程序只能有一個進(jìn)程。程序本身只是一個文本文件,不管它的后綴是什么,它是靜態(tài)的;但是程序,即使如Python一樣的解釋型語言最后都會被轉(zhuǎn)化為可以執(zhí)行的塊,也就是進(jìn)程,這是一個動態(tài)的概念。但是如果你在多個終端運(yùn)行你的程序,這不是多進(jìn)程,這相當(dāng)于多個程序?qū)?yīng)多個進(jìn)程。

當(dāng)有很多不同功能的進(jìn)程在操作系統(tǒng)里運(yùn)行時,操作系統(tǒng)負(fù)責(zé)管理它們,這就是進(jìn)程管理。操作系統(tǒng)需要保證當(dāng)其中某個進(jìn)程出現(xiàn)異常時(比如說長期占用I/O或不斷申請內(nèi)存)不會對其他進(jìn)程造成連鎖影響,因?yàn)檫@不利于操作系統(tǒng)的健康。為了保證這點(diǎn),操作系統(tǒng)會讓每個進(jìn)程處于相互隔離的狀態(tài)(分配獨(dú)立的地址空間),每個進(jìn)程都由自己獨(dú)立的PCB(Process Control Block)、數(shù)據(jù)塊和指令塊,其中PCB就是進(jìn)程的描述符

我們所寫的代碼會被編譯并作為一個進(jìn)程執(zhí)行,它們最終會被轉(zhuǎn)化為一條一條CPU指令,然后傳入CPU,由各種寄存器進(jìn)行運(yùn)算。CPU一般包含三種結(jié)構(gòu):控制器、運(yùn)算器和寄存器,它們是一些硬件結(jié)構(gòu),通過接收信號輸入(一串1/0組成的信號)進(jìn)行位運(yùn)算并輸出結(jié)果。寄存器根據(jù)功能不同又分為幾種類型,其中一種叫做程序計(jì)數(shù)器(program counter)簡稱PC。PC寄存器中的值永遠(yuǎn)代表了下一條將要執(zhí)行的指令,當(dāng)一條指令執(zhí)行完,CPU會讀取PC的值,然后根據(jù)解析的結(jié)果做出相應(yīng)的決策,最后PC會自加1,讓自己的值指向下一條指令。程序(本質(zhì)上是指令)也可以主動修改PC的值,來實(shí)現(xiàn)跳轉(zhuǎn),最直觀的就是C語言里的goto語句,循環(huán)語句、條件語句也是造成PC值修改的主要原因。如果將這些語句解析成指令,其中必然有一條是jmp [Addr] 指令,而這條指令其實(shí)就是修改了PC的值。如果想知道CPU指令究竟是如何被解析執(zhí)行的,這些指令又是如何相互組合成為一個功能塊的可以參考我之前用java寫的一個項(xiàng)目:CISC Computer Simulator,我們用程序模擬了一個虛擬機(jī),包含內(nèi)存、ROM、Cache和CPU(包含14個寄存器),并用它們來模擬大約三十幾個CPU指令的執(zhí)行方式(其中就包含跳轉(zhuǎn)指令),最后再用這些指令編寫了兩個小程序,一個是找20個數(shù)中的最大值,一個是搜索字符串,兩個程序都可以在虛擬機(jī)里運(yùn)行。

這里插一句題外話,這部分關(guān)于指令解析執(zhí)行的內(nèi)容并不是不推薦使用goto語句的原因。不推薦使用goto語句的原因主要是因?yàn)镃PU的分支預(yù)測器(Branch Predictor), 這塊我也不是很懂,我只知道BP在面臨條件跳轉(zhuǎn)的時候會預(yù)計(jì)算某個分支的指令,如果這部分代碼需要執(zhí)行,那CPU的性能就得到了提高;如果后來發(fā)現(xiàn)這部分指令不需要執(zhí)行,至少也維持了原先性能。從這點(diǎn)看,“不適用goto"語句其實(shí)是個偽命題,因?yàn)間oto語句對CPU運(yùn)算可能造成的損失至少是和循環(huán)或者條件語句是一樣的。寫到這里,我忽然想到有時候系統(tǒng)代碼里在寫if語句時,會在條件前面加個likely()或者unlikely(),當(dāng)時是說這樣會有助于編譯器識別更可能被執(zhí)行的語句,我不知道兩個方法和分支預(yù)測有沒有關(guān)系?

(上一段的內(nèi)容是我的一點(diǎn)想法,有可能是完全錯誤的。請謹(jǐn)慎閱讀,如果日后我找到了確定的答案,會回來補(bǔ)充的。)

回到進(jìn)程上來,假設(shè)計(jì)算機(jī)只有一個CPU,或者說這個CPU是單核的, 那么對于所有進(jìn)程來說它們需要共享一個CPU,而CPU一次只能運(yùn)行一個進(jìn)程。如果我們讓進(jìn)程按照順序依次執(zhí)行每個進(jìn)程,這顯然是不合理的,萬一其中一個進(jìn)程進(jìn)入死循環(huán)了呢?按照進(jìn)程優(yōu)先級執(zhí)行?讓最短的程序先執(zhí)行?最終操作系統(tǒng)選用了一種叫帶優(yōu)先級的時間片輪詢的算法,優(yōu)先級就是給每個進(jìn)程設(shè)置一個數(shù)字代表優(yōu)先級,數(shù)字越大優(yōu)先級越高;在進(jìn)程執(zhí)行時,CPU會進(jìn)行時長記錄,每個進(jìn)程最多只能執(zhí)行多少時間,時間一到CPU就會主動切換到下一個進(jìn)程。同時操作系統(tǒng)也會提供系統(tǒng)調(diào)用,允許進(jìn)程自行調(diào)整它或者它的子進(jìn)程的狀態(tài),比如說,如果一個進(jìn)程發(fā)送了一個I/O請求,進(jìn)入等待狀態(tài),而當(dāng)前的時間片還沒結(jié)束,進(jìn)程就可以主動掛起自己,讓CPU調(diào)度下一個進(jìn)程。

這里有一個問題就是CPU是如何進(jìn)行進(jìn)程切換的。答案是,在切換前,CPU會把所有的寄存器的內(nèi)容,包括PC的值都保存到當(dāng)前進(jìn)程的PCB里;然后再從下一個進(jìn)程的PCB里讀取所有寄存器的內(nèi)容,如果下個進(jìn)程是個新的進(jìn)程,PC值就只想這個進(jìn)程的入口地址,如果下個進(jìn)程是之前切換過的,那么它的PCB里保存的就是當(dāng)時切換前的狀態(tài),把這些值讀入CPU后,它就可以接著執(zhí)行了。這些被保存或者被讀取的用來在CPU中執(zhí)行的內(nèi)容,被稱為這個進(jìn)程的上下文(context), 總結(jié)起來就是讀取進(jìn)程A上下文->執(zhí)行進(jìn)程A->時間到,保存進(jìn)程A上下文->讀取進(jìn)程B上下文->執(zhí)行進(jìn)程B->...,至于進(jìn)程ABCD...的順序由調(diào)度算法決定。雖然這個步驟看起來很復(fù)雜,但事實(shí)上CPU的執(zhí)行速度非???,快到人類根本感覺不出來,這也是為什么你在用電腦的時候會覺得所有的程序都是在一起運(yùn)行的。但在CPU看來,它們是一個一個執(zhí)行的。

理論上操作系統(tǒng)會按照這個序列一直執(zhí)行到所有進(jìn)程結(jié)束,而事實(shí)上這是不可能發(fā)生的,因?yàn)橛衖nit進(jìn)程的存在,init進(jìn)程會在操作系統(tǒng)boot后創(chuàng)建,它一直都在。操作系統(tǒng)的進(jìn)程是以init進(jìn)程為root的樹形結(jié)構(gòu),進(jìn)程可以不斷產(chǎn)生新的進(jìn)程,也會有進(jìn)程被銷毀,這些可以通過system call實(shí)現(xiàn)。雖然進(jìn)程是隔離的,但是進(jìn)程之間卻是需要通信的或者說是數(shù)據(jù)傳遞的。有時候進(jìn)程會需要同時訪問一個資源,比如說打印機(jī),此時就會產(chǎn)生資源競爭的問題。

前一個問題很好解決,進(jìn)程通信目前有很多方式:管道、信號、消息隊(duì)列、共享內(nèi)存、套接字等等,操作系統(tǒng)都會提供相應(yīng)的接口函數(shù)。其中套接字不僅可以進(jìn)行本地通信,還可以實(shí)現(xiàn)與另一臺機(jī)器上的進(jìn)程進(jìn)行通信,這就是協(xié)議的底層,再套接字的基礎(chǔ)上,有點(diǎn)進(jìn)程會再細(xì)化出各種通信協(xié)議,比如說HTTP服務(wù)端,它本質(zhì)上是運(yùn)行在某個主機(jī)上的進(jìn)程,一般通過偵聽80端口來實(shí)現(xiàn)和HTTP客戶端的通信,而這些HTTP客戶端本質(zhì)上也是某個主機(jī)的一個進(jìn)程。

第二個問題則要引入的概念,鎖允許一個進(jìn)程獨(dú)占某個潛在共享資源,多數(shù)時候是一段內(nèi)存,這段內(nèi)存可以是一個列表或者一個變量。使用鎖的時候要注意盡量小的包含代碼塊,因?yàn)橐坏┠巢糠仲Y源(對應(yīng)的是一段代碼)被某個進(jìn)程上了鎖,不管這個進(jìn)程在不在CPU中執(zhí)行,只要它沒有釋放鎖,其他進(jìn)程都不能訪問這部分資源。鎖的類型有很多,設(shè)計(jì)的不好也會出現(xiàn)問題,比如死鎖,這里就不講開來說了。私以為對于編程者來說,了解這些固然好,如果不了解,至少要知道寫多進(jìn)程代碼時,要考慮到共享資源的問題。

我之前參照GO語言的線程實(shí)現(xiàn)方式用C寫了一個輕量級的線程庫,注意是線程庫Lightweight-Thread-Library。它實(shí)現(xiàn)了一個用戶層的線程庫,支持線程的創(chuàng)建、跳轉(zhuǎn)、加入和銷毀,線程調(diào)度,使用通道或者組進(jìn)行線程通信。并成功移植到另一個操作系統(tǒng)中遵循操作系統(tǒng)的進(jìn)程調(diào)度。這個庫沒有實(shí)現(xiàn)鎖的機(jī)制。

最后還有一個小問題,如果CPU是多核的呢?情況就更復(fù)雜了,因?yàn)槌松鲜龅牟僮骱皖檻],多個核心之間也要通信,因?yàn)橛锌赡懿煌诵纳系倪M(jìn)程要訪問同一塊內(nèi)存,那么當(dāng)其中一個進(jìn)程給這段內(nèi)存上鎖之前,它可能需要廣播一條消息,用來確保所有其他核心上的進(jìn)程都不在訪問這段內(nèi)存。這就涉及到多核架構(gòu)下的一個重要課題:一致性(consistence),同樣的,太復(fù)雜了,這里就不說了。

關(guān)于進(jìn)程有幾篇推薦的文章:操作系統(tǒng)之進(jìn)程的幾種狀態(tài)、進(jìn)程管理的總結(jié) 、操作系統(tǒng)之進(jìn)程管理、Linux進(jìn)程間通信的幾種方式總結(jié)

進(jìn)程和線程

首先分享一個關(guān)于進(jìn)程和線程的比喻,很有趣:進(jìn)程與線程的一個簡單解釋。

某種程度上來說,操作系統(tǒng)和進(jìn)程的關(guān)系與進(jìn)程和線程的關(guān)系有點(diǎn)類似。還記得上述提到的CPU的時間片段嗎?這在早期計(jì)算機(jī)能力不是很強(qiáng)的時候,執(zhí)行起來完全沒問題,進(jìn)程體系完美完成了它提高系統(tǒng)執(zhí)行效率的任務(wù)。但是隨著計(jì)算機(jī)的發(fā)展,多核體系的出現(xiàn),人們發(fā)現(xiàn)原本的進(jìn)程體系過于累贅。因?yàn)橛?jì)算機(jī)的能力提升了,那么同樣的條件下,現(xiàn)在可以新建的進(jìn)程自然也增加了,那么操作系統(tǒng)進(jìn)行內(nèi)存管理的開銷也隨之增加了,這些開銷包括進(jìn)程的新建、切換和銷毀。那么要進(jìn)一步提高計(jì)算的效率,減少這些開銷是一個研究的方向。

我琢磨著,當(dāng)時并沒有一個完美的可以用來替代原本的進(jìn)程管理體系的解決方案,能做的只是諸多嘗試,其中一種就是把一部分內(nèi)容從進(jìn)程中分割出來,這部分就是后來的線程。

那么是哪些東西從進(jìn)程中分割出來了?在原本的體系中,進(jìn)程就是CPU執(zhí)行的基本單位,它有它自己的獨(dú)立于其他進(jìn)程的地址空間、擁有完成某項(xiàng)功能的能力、也可以與其他進(jìn)程通信。在新的體系中,原本的進(jìn)程只保留了其地址空間,而線程則脫離出來,繼承了它的執(zhí)行能力和通信能力,取代進(jìn)程成為了CPU可以調(diào)度的基本單位,而原本的進(jìn)程除了擁有地址空間,還負(fù)責(zé)屬于它的那些線程的管理。

那么從此處開始,之后提到的進(jìn)程都是新體系中的進(jìn)程,之前的進(jìn)程會被成為舊版進(jìn)程。我在網(wǎng)上看到一句話叫linux不區(qū)分進(jìn)程和線程我以為應(yīng)該是不區(qū)分線程和舊版進(jìn)程。接下來的問題是,這樣做的好處是什么?提高系統(tǒng)效率是肯定的,我從網(wǎng)上看到一個描述叫使得并發(fā)的顆粒度更細(xì), 但是具體表現(xiàn)在哪些方面呢?

  • 線程是由進(jìn)程管理的,使用的都是進(jìn)程已有的資源,相對于舊版的進(jìn)程,線程擁有更小的控制塊和數(shù)據(jù)塊,這使得線程更輕盈,管理起來也更方便。

  • 還記得上文中關(guān)于舊版進(jìn)程切換時的過程嗎?在這里,因?yàn)槎际鞘褂枚际沁M(jìn)程的資源,同一個進(jìn)程下,如果線程切換,不會引起進(jìn)程的切換;如果切換的線程屬于另一個進(jìn)程,才會引起進(jìn)程切換。同時,因?yàn)榫€程更輕盈,切換起來也更快。

  • 一個操作系統(tǒng)至少擁有一個進(jìn)程,一個進(jìn)程至少擁有一個線程。換句話說,一個進(jìn)程可以有很多個線程,這些線程共享這個進(jìn)程的資源。

簡而言之,線程更小、更輕盈,提高了系統(tǒng)的并發(fā)行,還節(jié)省了系統(tǒng)的開銷。

線程的實(shí)現(xiàn)

關(guān)于線程的實(shí)現(xiàn),我找到了一片文檔:線程的3種實(shí)現(xiàn)方式--內(nèi)核級線程, 用戶級線程和混合型線程,個人很推薦。我上文中所提的輕量級線程庫就是用戶級線程,在CompositeOS下測試的時候,它工作在用戶層,程序初始話的時候會向內(nèi)核申請一大段內(nèi)存,之后會向創(chuàng)建的線程分配這些內(nèi)存;同時也支持內(nèi)核級線程的調(diào)度。

以上就是我對操作系統(tǒng)、進(jìn)程、線程的理解。

協(xié)程

協(xié)程是線程的進(jìn)一步剝離,相對于線程,協(xié)程進(jìn)一步抽出了線程切換,它作用的對象也進(jìn)一步縮小。線程至少也有一個完整的入口、代碼、出口的流程,而協(xié)程可能只有一個代碼塊。而線程負(fù)責(zé)調(diào)度這些代碼塊使得這些代碼塊協(xié)同工作,可能這就是協(xié)程名字的由來。回想一下三者的英文名:process, thread, coroutine,感受一下。

關(guān)于協(xié)程的資料不多,但是因?yàn)镻ython 3中有協(xié)程的實(shí)現(xiàn),所以略作介紹,它的維基地址:協(xié)程。

值得一提的是,雖然三者看似是個高中低三檔的模式,但是實(shí)際編程的話,要根據(jù)代碼的特性選擇合適的方式,比如說項(xiàng)目是不是CPU密集運(yùn)算的?或者是I/O密集運(yùn)算?這些都是需要考慮的,后面會說。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 又來到了一個老生常談的問題,應(yīng)用層軟件開發(fā)的程序員要不要了解和深入學(xué)習(xí)操作系統(tǒng)呢? 今天就這個問題開始,來談?wù)劜?..
    tangsl閱讀 4,310評論 0 23
  • 操作系統(tǒng)概論 操作系統(tǒng)的概念 操作系統(tǒng)是指控制和管理計(jì)算機(jī)的軟硬件資源,并合理的組織調(diào)度計(jì)算機(jī)的工作和資源的分配,...
    野狗子嗷嗷嗷閱讀 12,450評論 3 34
  • 讀書有感,如圖: 1.執(zhí)行力 幫老板解決問題,達(dá)到他的kpi才是好下屬 2.溝通能力 溝通,及時請教,避免掉坑,事...
    fish666閱讀 681評論 0 0
  • 打卡報(bào)到!來晚了,比沒來要好。
    八面閱讀 69評論 0 0
  • 下雨了 而你在何方 是否早已被雨滴打濕 迷茫了方向 暮色蒼白 你在哪里的窗沿 消磨苦短 獨(dú)訴情感 與空樓相伴
    風(fēng)蕭虎嘯人難笑閱讀 178評論 0 0

友情鏈接更多精彩內(nèi)容