一、聊聊并發(fā)這件事
在基礎(chǔ)系列我們學(xué)習(xí)了Go的并發(fā)編程田巴,對并發(fā)的概念已經(jīng)有了一定的了解壹哺。在各種現(xiàn)代高級語言中,對并發(fā)的支持已經(jīng)是標(biāo)配截珍,但Go的并發(fā)無論在開發(fā)效率還是在性能上都有相當(dāng)?shù)膬?yōu)越性笛臣。Go有什么獨(dú)特的設(shè)計(jì)讓其在并發(fā)編程領(lǐng)域獨(dú)步江湖沈堡?這得益于Go的并發(fā)調(diào)度器燕雁。
我們知道拐格,軟件在系統(tǒng)運(yùn)行的基本單元是進(jìn)程,由進(jìn)程開辟出多條線程懂衩,而線程則是CPU調(diào)度的基本單位浊洞。我們常說千級并發(fā)法希、萬級并發(fā)靶瘸。甚至百萬級并發(fā),是那么多線程真正同一時(shí)間執(zhí)行嗎屋剑?其實(shí)并不是饼丘,程序同一時(shí)間的并發(fā)數(shù)完全由物理上的CPU核心數(shù)決定肄鸽,即同一時(shí)間單位一個(gè)CPU核心只能執(zhí)行一條線程的計(jì)算,其多線程的并發(fā)完全由調(diào)度器決定蟀苛。
二帜平、并發(fā)模型概述
各種高級語言的調(diào)度器設(shè)計(jì)各不相同裆甩,但基本可以分成三種模型設(shè)計(jì)齐唆,第一種為內(nèi)核級并發(fā)模型(1:1)箍邮,即內(nèi)核線程和用戶線程綁定;第二種為用戶級并發(fā)模型(N:1)堪澎,由用戶維護(hù)線程的管理樱蛤,其內(nèi)核線程和用戶線程的調(diào)度由調(diào)度器實(shí)現(xiàn)刹悴;第三種為兩級調(diào)度的混合并發(fā)模型(N:M),這種集前兩種模型的優(yōu)勢磕道,完全由語言運(yùn)行時(shí)的調(diào)度器控制概作,是實(shí)現(xiàn)最為復(fù)雜的一種田度。下面我們分別了解一下這三種并發(fā)模型:
1.內(nèi)核級并發(fā)模型(1:1)
所謂內(nèi)核線程镇饺,即物理線程,可以被操作系統(tǒng)內(nèi)核調(diào)度器調(diào)度的對象實(shí)體惋啃,是操作系統(tǒng)內(nèi)核的最小調(diào)度單元边灭。這里用戶開辟的每一個(gè)線程和內(nèi)核線程綁定(1:1)健盒,線程的調(diào)度完全由系統(tǒng)內(nèi)核管理,應(yīng)用程序?qū)€程幾乎沒有管理惰帽,大部分語言的線程庫该酗,如JAVA频轿,都是對操作系統(tǒng)內(nèi)核線程的封裝。
優(yōu)點(diǎn):
- 實(shí)現(xiàn)簡單耕赘,直接扔給操作系統(tǒng)內(nèi)核管理操骡;
- 線程切換由CPU實(shí)現(xiàn)赚窃,可以有效利用CPU的多核;
缺點(diǎn):
- CPU管理的線程切換涉及線程的上下文是掰、資源調(diào)度键痛,因此切換成本很高;
- 不宜開辟過多的并發(fā)量絮短,內(nèi)核線程的堆棧空間在 Windows 下默認(rèn) 2M杉允,Linux 下默認(rèn) 8M叔磷,過多的并發(fā)量導(dǎo)致的切換開銷對性能影響是很大的胁勺。
2.用戶級并發(fā)模型(N:1)
所謂用戶級并發(fā)模型,是指單個(gè)進(jìn)程內(nèi)部管理的多條線程寥裂,線程的調(diào)度完全由用戶進(jìn)程決定封恰,一個(gè)進(jìn)程中的所有線程都只和一個(gè)CPU內(nèi)核線程在運(yùn)行時(shí)動(dòng)態(tài)綁定(N:1)褐啡。即CPU內(nèi)核線程的調(diào)度器對用戶進(jìn)程內(nèi)部的多條線程是無感知的,內(nèi)核線程只知道用戶進(jìn)程低飒。像python褥赊、nodejs等語言的并發(fā)實(shí)現(xiàn)就是這種模型莉恼,簡單來說這是一種偽并發(fā),因?yàn)橥粫r(shí)間內(nèi)允許的線程只有一個(gè)尿背。
優(yōu)點(diǎn):
- 線程調(diào)度在用戶層面田藐,CPU不需要在用戶態(tài)和內(nèi)核態(tài)切換,資源開銷很熊罱;
- 由于沒有上下文切換帶來的開銷市袖,其用戶線程比較輕量化,可以開啟較多的用戶線程酒觅。
缺點(diǎn):
- 并不能做到真正意義上的并發(fā)舷丹,其本質(zhì)還是單核計(jì)算蜓肆,對于IO阻塞的任務(wù)還是會被中斷;
- 需要線程庫把IO阻塞的操作重新封裝為完全的非阻塞形式症概,然后在以前要阻塞的點(diǎn)上彼城,主動(dòng)讓出自己退个,并通過某種方式通知或喚醒其他待執(zhí)行的用戶線程在內(nèi)核線程上運(yùn)行
3.混合型并發(fā)模型(N:M)
以上兩種調(diào)度器模型都有優(yōu)缺點(diǎn)语盈,有沒有可能取長補(bǔ)短設(shè)計(jì)新的調(diào)度器模型呢?混合型并發(fā)模型就是博采眾長之后的產(chǎn)物习柠,充分吸收前兩種線程模型的優(yōu)點(diǎn)且盡量規(guī)避它們的缺點(diǎn)资溃×姨浚混合型并發(fā)模型是讓內(nèi)核線程和用戶線程建立多對多的關(guān)系(N:M),即一個(gè)用戶進(jìn)程可以和多個(gè)內(nèi)核線程關(guān)聯(lián)趴捅,相當(dāng)于用戶進(jìn)程內(nèi)部開辟的多個(gè)用戶線程可以動(dòng)態(tài)綁定多個(gè)內(nèi)核線程。這種模型避免了內(nèi)核級模型中的完全靠操作系統(tǒng)調(diào)度的并發(fā)性能問題综芥,也避免了用戶級模型中的偽并發(fā)問題猎拨,它是用戶自身調(diào)度器和系統(tǒng)調(diào)度器協(xié)同工作的設(shè)計(jì)红省。
優(yōu)點(diǎn):
- 充分博采眾長,實(shí)現(xiàn)真正意義上的高效能并發(fā)虾啦;
- 對用戶友好痕寓,用戶只需管理業(yè)務(wù)層面的并發(fā)任務(wù)。
缺點(diǎn):
- 由于這種模型需用用戶級調(diào)度和系統(tǒng)級調(diào)度協(xié)同工作需频,所以這種調(diào)度器實(shí)現(xiàn)都相當(dāng)復(fù)雜昭殉。
三挪丢、Go并發(fā)調(diào)度器解析
Go調(diào)度器中的三種結(jié)構(gòu)G卢厂、P、M
系統(tǒng)線程固定2M任内,且維護(hù)一堆上下文融柬,對需求多變的并發(fā)應(yīng)用并不友好粒氧,有可能造成內(nèi)存浪費(fèi)或內(nèi)存不夠用。Go將并發(fā)的單位下降到線程以下摘盆,由其設(shè)計(jì)的goroutine初始空間非常小,僅2kb狼渊,但支持動(dòng)態(tài)擴(kuò)容到最大1G类垦,這就是go自己的并發(fā)單元——goroutine協(xié)程护锤。
實(shí)際上系統(tǒng)最小的執(zhí)行單元仍然是線程烙懦,go運(yùn)行時(shí)執(zhí)行的協(xié)程也是掛載到某一系統(tǒng)線程之上的赤炒,這種協(xié)程與系統(tǒng)線程的調(diào)度分配由Go的并發(fā)調(diào)度器承擔(dān),Go的并發(fā)調(diào)度器是屬于混合的二級調(diào)度并發(fā)模型掩缓,其內(nèi)部設(shè)計(jì)有G你辣、P尘执、M三種抽象結(jié)構(gòu),我們來看一下它們分別是什么:
G-P-M模型抽象結(jié)構(gòu):
- G: 表示Goroutine表悬,每個(gè)Goroutine對應(yīng)一個(gè)G結(jié)構(gòu)體蟆沫,G存儲Goroutine的運(yùn)行堆棧温治、狀態(tài)以及任務(wù)函數(shù),可重用但绕。G運(yùn)行隊(duì)列是一個(gè)棧結(jié)構(gòu),分全局隊(duì)列和P綁定的局部隊(duì)列六孵,每個(gè)G不能獨(dú)立運(yùn)行劫窒,它需要綁定到P才能被調(diào)度執(zhí)行拆座。
- P: Processor,表示邏輯處理器孕索, 對G來說搞旭,P相當(dāng)于CPU核菇绵,G只有綁定到P(在P的local runq中)才能被調(diào)度。對M來說翎嫡,P提供了相關(guān)的執(zhí)行環(huán)境(Context)永乌,如內(nèi)存分配狀態(tài)(mcache)翅雏,任務(wù)隊(duì)列(G)等,P的數(shù)量決定了系統(tǒng)內(nèi)最大可并行的G的數(shù)量(前提:物理CPU核數(shù) >= P的數(shù)量)碗脊,P的數(shù)量由用戶設(shè)置的GOMAXPROCS決定橄妆,但是不論GOMAXPROCS設(shè)置為多大,P的數(shù)量最大為256矢劲。
- M: Machine芬沉,系統(tǒng)物理線程,代表著真正執(zhí)行計(jì)算的資源丸逸,在綁定有效的P后,進(jìn)入schedule循環(huán)捎谨;而schedule循環(huán)的機(jī)制大致是從Global隊(duì)列涛救、P的Local隊(duì)列以及wait隊(duì)列中獲取G业扒,切換到G的執(zhí)行棧上并執(zhí)行G的函數(shù),調(diào)用goexit做清理工作并回到M咧栗,如此反復(fù)。M并不保留G狀態(tài)交煞,這是G可以跨M調(diào)度的基礎(chǔ),M的數(shù)量是不定的集嵌,由Go Runtime調(diào)整御毅,為了防止創(chuàng)建過多OS線程導(dǎo)致系統(tǒng)調(diào)度不過來端蛆,目前默認(rèn)最大限制為10000個(gè)。
關(guān)于P這個(gè)設(shè)計(jì)嫌拣,是在Go1.0之后才實(shí)現(xiàn)的呆躲,起初的Go并發(fā)性能并不十分亮眼,協(xié)程和系統(tǒng)線程的調(diào)度比較粗暴灰瞻,導(dǎo)致很多性能問題,如全局資源鎖燎竖、M的內(nèi)存過高等造成許多性能損耗袍祖,加入P的設(shè)計(jì)后實(shí)現(xiàn)了一個(gè)叫做 work-stealing 的調(diào)度算法:由P來維護(hù)Goroutine隊(duì)列并選擇一個(gè)適當(dāng)?shù)腗綁定。
G-P-M模型調(diào)度
我們來看看go關(guān)鍵字創(chuàng)建一個(gè)協(xié)程后其調(diào)度器是怎么工作的:
- go關(guān)鍵字創(chuàng)建goroutine(G)捐凭,優(yōu)先加入某個(gè)P維護(hù)的局部隊(duì)列(當(dāng)局部隊(duì)列已滿時(shí)才加入全局隊(duì)列)茁肠;
- P需要持有或者綁定一個(gè)M垦梆,而M會啟動(dòng)一個(gè)系統(tǒng)線程仅孩,不斷的從P的本地隊(duì)列取出G并執(zhí)行;
- M執(zhí)行完P(guān)維護(hù)的局部隊(duì)列后京腥,它會嘗試從全局隊(duì)列尋找G公浪,如果全局隊(duì)列為空船侧,則從其他的P維護(hù)的隊(duì)列里竊取一般的G到自己的隊(duì)列;
- 重復(fù)以上知道所有的G執(zhí)行完畢预柒。
當(dāng)然也有一些情況會造成Goroutine阻塞袁梗,如:
- 系統(tǒng)GC围段;
- 系統(tǒng)IO資源的調(diào)用,如文件讀寫适贸;
- 網(wǎng)絡(luò)IO的延遲;
- 管道阻塞拜姿;
- 同步操作。
當(dāng)遇到上述阻塞時(shí)谒获,Go調(diào)度器也有相應(yīng)的處理方式:
- 1.系統(tǒng)調(diào)度引起阻塞:
如系統(tǒng)GC批狱,M會解綁P展东,出讓控制權(quán)給其他M,讓該P(yáng)維護(hù)的G運(yùn)行隊(duì)列不至于阻塞爪膊。
- 2.用戶態(tài)的阻塞:
當(dāng)goroutine因?yàn)楣艿啦僮骰蛘呦到y(tǒng)IO推盛、網(wǎng)絡(luò)IO而阻塞時(shí)谦铃,對應(yīng)的G會被放置到某個(gè)等待隊(duì)列,該G的狀態(tài)由運(yùn)行時(shí)變?yōu)榈却隣顟B(tài),而M會跳過該G嘗試獲取并執(zhí)行下一個(gè)G件豌,如果此時(shí)沒有可運(yùn)行的G供M運(yùn)行茧彤,那么M將解綁P,并進(jìn)入休眠狀態(tài)惫谤;當(dāng)阻塞的G被另一端的G2喚醒時(shí)珠洗,如管道通知,G又被標(biāo)記為可運(yùn)行狀態(tài)蝴猪,嘗試加入G2所在P局部隊(duì)列的隊(duì)頭,然后再是G全局隊(duì)列自阱。
- 3.當(dāng)存在空閑的P時(shí),竊取其他隊(duì)列的G:
當(dāng)P維護(hù)的局部隊(duì)列全部運(yùn)行完畢趋箩,它會嘗試在全局隊(duì)列獲取G叫确,直到全局隊(duì)列為空哼丈,再向其他局部隊(duì)列竊取一般的G。
至此Go的調(diào)度器模型解析完畢饶米〕岛基于Go調(diào)度器的優(yōu)越設(shè)計(jì),它號稱能實(shí)現(xiàn)百萬級并發(fā)匈棘,即使日常很難達(dá)到這種并發(fā)量主卫,我們也應(yīng)該對并發(fā)的使用要心存敬畏,真正的并發(fā)依賴于物理核心完域,啟動(dòng)并發(fā)是需要系統(tǒng)開銷的瘩将,雖然在Go的運(yùn)行時(shí)它看起來很小,但量變引起質(zhì)變肠仪,當(dāng)業(yè)務(wù)啟動(dòng)的并發(fā)到十萬級备典、百萬級甚至千萬級時(shí)提佣,其性能開銷還是非常巨大的欲险∑ヤ蹋可以通過一定的手段控制并發(fā)數(shù)量以防止系統(tǒng)奔潰,如實(shí)現(xiàn)一個(gè)協(xié)程池喜每,通過worker機(jī)制控制并發(fā)數(shù)带兜。
Ok吨灭,希望學(xué)完這一專題你會對Go的并發(fā)有更深刻的了解。