導(dǎo)讀
要理解進(jìn)程與線程是偷,首先得了解并發(fā)與并行感挥。
并發(fā)與并行
- 并發(fā)
單核CPU時(shí)間分片,多個(gè)程序切換執(zhí)行冻辩,這就是并發(fā)猖腕。并發(fā)是共享內(nèi)存的,所以需要加鎖恨闪。
因?yàn)椴l(fā)共享內(nèi)存倘感,并且會(huì)幾個(gè)程序互相切換執(zhí)行,所以在一個(gè)CPU執(zhí)行的并發(fā)必須處理上下文切換的問(wèn)題咙咽。 進(jìn)程就是在這種背景下老玛,被提出的。進(jìn)程就是一些相關(guān)線程的統(tǒng)稱钧敞,是一些相關(guān)線程的集合蜡豹。進(jìn)程搭配虛擬內(nèi)存、進(jìn)行表等溉苛,可以管理獨(dú)立程序的運(yùn)行余素、切換。
并發(fā)也就是多線程炊昆。
由上面這段話可見,程序在運(yùn)行過(guò)程中威根,對(duì)計(jì)算機(jī)資源的分配是個(gè)很重大的問(wèn)題凤巨,于是出現(xiàn)了操作系統(tǒng)專門干這個(gè)活。操作系統(tǒng)核心的操作是陷入內(nèi)核(Kernel)洛搀,切換到操作系統(tǒng)敢茁,讓內(nèi)核來(lái)做。
- 并行
多核CPU幾個(gè)程序同時(shí)執(zhí)行留美,這就是并行彰檬。并行也就是我們所說(shuō)的多進(jìn)程。
進(jìn)程和線程
- 進(jìn)程
進(jìn)程是一個(gè)容器谎砾,也就是一個(gè)程序逢倍。
進(jìn)程的切換:
- 切換頁(yè)全局目錄(Page Global Directory)來(lái)加載新的地址空間,實(shí)際上會(huì)加載新進(jìn)程的cr3寄存器值景图。
- 切換內(nèi)核堆棧和硬件上下文较雕,這些包含了內(nèi)核執(zhí)行一個(gè)新進(jìn)程的所有信息,包含了CPU寄存器。
-
線程
線程是容器里的工作單元亮蒋。上面說(shuō)并發(fā)的時(shí)候講到扣典,并發(fā)是共享內(nèi)存的,也就說(shuō)這些并發(fā)的線程會(huì)共享一個(gè)地址空間慎玖。線程的切換贮尖,不需要重新加載地址空間,頁(yè)面緩沖區(qū)趁怔,需要切換寄存器上下文和棧湿硝。開銷相對(duì)較小。線程在切換任務(wù)的時(shí)候痕钢,切換寄存器上下文和棧是搶占式的(Preeemptive multitasking)图柏,誰(shuí)搶了是誰(shuí)的,這就導(dǎo)致了線程之間的執(zhí)行順序是無(wú)法保證的任连,所以使用線程時(shí)需要小心操作同步問(wèn)題蚤吹。
進(jìn)程和線程的相同點(diǎn)
線程和進(jìn)程的切換,都需要陷入系統(tǒng)調(diào)用随抠,即CPU先跑操作系統(tǒng)的調(diào)度程序裁着,然后再由調(diào)度程序決定該炮哪一個(gè)進(jìn)程(線程)
同步和異步
首先要明確一點(diǎn),同步/異步這個(gè)討論對(duì)于IO密集型才有意義拱她。
因?yàn)閷?duì)于計(jì)算密集型程序來(lái)說(shuō)二驰,
你等或者不等,
計(jì)算任務(wù)都在那里秉沼,
不多不少桶雀。
對(duì)于IO密集型程序來(lái)說(shuō),
你等就是占著茅坑不拉屎唬复。你的肚子還沒有應(yīng)答矗积,你蹲在那有什么用?反而不如騰出地方來(lái)敞咧,讓程序別的地方先用著棘捣。等你確實(shí)要拉了,再來(lái)休建。這個(gè)坑位乍恐,就是CPU啊测砂!
協(xié)程
協(xié)程茵烈,coroutine,也叫纖程(Fiber),或綠色線程砌些。
協(xié)程是用戶態(tài)的輕量級(jí)線程瞧毙。這句話是什么鬼?
協(xié)程擁有自己的寄存器上下文和棧。協(xié)程調(diào)度切換時(shí)宙彪,將寄存器上下文和棧保存到其他地方矩动,切回來(lái)的時(shí)候,揮發(fā)原來(lái)保存的寄存器上下文和棧释漆。
由此可見悲没,協(xié)程能保留上一次調(diào)用時(shí)的狀態(tài)。協(xié)程的任務(wù)切換是由用戶自己控制的男图,不存在搶占示姿,所以這種叫做協(xié)作式多任務(wù)。
協(xié)程也是單線程的逊笆,本質(zhì)也是異步+回調(diào)栈戳,但是它是經(jīng)過(guò)包裝的,寫出的代碼看著是同步的代碼难裆。寫過(guò)Node的應(yīng)該深有體會(huì)子檀。
Go研究出來(lái)了一套協(xié)程的調(diào)度算法,所以Go的協(xié)程叫做Goroutine乃戈。
Kotlin也有褂痰,C#也有,不是什么新鮮玩意症虑。但是在語(yǔ)言的層面上的支持缩歪,還是第一個(gè)。
事件驅(qū)動(dòng)
事件驅(qū)動(dòng)是事先編寫一個(gè)事件循環(huán)谍憔,這個(gè)事件循環(huán)程序不斷地檢查目前要處理的信息匪蝙,多用在GUI框架、頁(yè)面上的JS事件习贫。如果這個(gè)事件收到了要處理的事件的信號(hào)逛球,就異步去執(zhí)行。
然后這個(gè)信號(hào)是推拉結(jié)合的沈条。
基于事件驅(qū)動(dòng)的編程是單線程思維,特點(diǎn)是異步+回調(diào)诅炉。
協(xié)程的優(yōu)劣
協(xié)程的優(yōu)點(diǎn)
- 跨平臺(tái)/跨體系架構(gòu)
- 與進(jìn)程/線程相比蜡歹,上下文切換的開銷
- 與線程相比,不需要原子操作鎖定及同步
- 與事件驅(qū)動(dòng)相比涕烧,方便切換控制流月而,編程模型簡(jiǎn)單
- 高并發(fā),一個(gè)CPU支持上萬(wàn)的協(xié)程
協(xié)程的缺點(diǎn)
- 無(wú)法利用多核資源议纯,事件驅(qū)動(dòng)父款,本質(zhì)是個(gè)單線程的循環(huán)而已。需要配合進(jìn)程來(lái)執(zhí)行。
演進(jìn)軌跡
IO密集型程序:多進(jìn)程 -> 多線程 -> 事件驅(qū)動(dòng) -> 協(xié)程
CPU密集型程序:多進(jìn)程 -> 多線程
-
為什么從多進(jìn)程到多線程是一種進(jìn)步憨攒?
因?yàn)榫€程比進(jìn)行性能開銷小世杀,而且多線程之間數(shù)據(jù)通信與同步更加方便。想同步的時(shí)候就訪問(wèn)肝集,修改的時(shí)候加把鎖就可以了瞻坝。這個(gè)也是符合我們直覺的。比如要需要一個(gè)程序某個(gè)部分的幾個(gè)不同運(yùn)算值杏瞻,是在后臺(tái)多起幾個(gè)進(jìn)程分別跑呢所刀?還是寫成多線程處理呢?實(shí)踐中捞挥,我們很少見到開多進(jìn)程的吧浮创?
那為什么CPU密集型任務(wù)沒有發(fā)展到事件驅(qū)動(dòng)、協(xié)程呢砌函?
因?yàn)镃PU密集型的任務(wù)斩披,一般都是需要計(jì)算的,計(jì)算是需要CPU的胸嘴。CPU的多少是和系統(tǒng)的硬件相關(guān)雏掠,異步是改變不了這一點(diǎn)的,等待的時(shí)候不能計(jì)算劣像,等待完了該算的還得接著算乡话,所以說(shuō)沒什么卵用。
多進(jìn)程(并行)/多線程(并發(fā))/協(xié)程各自的適用場(chǎng)景
為什么程序多了電腦卡
進(jìn)程多了耳奕,操作系統(tǒng)會(huì)頻繁切換進(jìn)程绑青。從上面可知,進(jìn)程一切換屋群,頁(yè)全局目錄闸婴、內(nèi)核堆棧、硬件上下文都會(huì)變芍躏。一旦進(jìn)程變多之后邪乍,頻繁切換會(huì)擠占大部分資源。
相關(guān)知識(shí)補(bǔ)充
啥是“陷入內(nèi)核”对竣?看到這個(gè)詞的第一反應(yīng)是寫錯(cuò)了庇楞。其實(shí)不然。
要說(shuō)請(qǐng)首先需明確否纬,特權(quán)級(jí)的概念吕晌。就行l(wèi)inux系統(tǒng)里面,喜歡劃分管理員和用戶等不同權(quán)限角色一樣临燃。在系統(tǒng)中睛驳,為了讓資源調(diào)配集中管理烙心,便有了特權(quán)級(jí)這個(gè)概念。特權(quán)級(jí)一共有0-3這四個(gè)級(jí)別乏沸,0最高淫茵,可以管理CPU。
linux系統(tǒng)里面只有兩個(gè)級(jí)別屎蜓,0級(jí)和3級(jí)痘昌。操作系統(tǒng)的內(nèi)核當(dāng)然是最高級(jí)0級(jí),應(yīng)用程序是3級(jí)炬转。當(dāng)應(yīng)用程序需要訪問(wèn)系統(tǒng)資源時(shí)辆苔,CPU就進(jìn)入了內(nèi)核,從圖上看好像陷進(jìn)入一樣扼劈。
所謂的上下文驻啤,是指程序在運(yùn)行過(guò)程中的一些中間狀態(tài),比如會(huì)運(yùn)算出來(lái)一些值荐吵,這些值在后面的計(jì)算中也會(huì)用到骑冗。當(dāng)然這些值必須保存在內(nèi)存中。