操作系統(tǒng)的調(diào)度模型是大致上有兩種N:1和1:1. N:1模型中用戶態(tài)的線程運(yùn)行在一個(gè)內(nèi)核線程上喊积,這種方式上下文切換快梢夯,但不能有效利用多核呛谜。
1:1模型中的一個(gè)用戶態(tài)線程對(duì)應(yīng)一個(gè)內(nèi)核線程清酥,這種方式有效利用了多核讶踪,但上下文切換非常慢(因?yàn)橐煌rap陷入內(nèi)核)。Goroutine是協(xié)程萌京,即輕量級(jí)線程,用戶態(tài)完成調(diào)度宏浩,Rob Pike先生想利用有限的os線程去調(diào)度和執(zhí)行任意數(shù)量的goroutine知残,顯然是想把N:1模型和1:1模型的優(yōu)點(diǎn)都收入囊中。
不要一下子就掉入源碼去看go的實(shí)現(xiàn)比庄,看下我們能想到的調(diào)度模型: ?線程+任務(wù)隊(duì)列.
其中G就是goroutine求妹,先開一個(gè)線程池,線程不停地從queue中取goroutine執(zhí)行佳窑。如果一個(gè)goroutine里面出現(xiàn)了長(zhǎng)時(shí)間系統(tǒng)調(diào)用制恍,那么就會(huì)阻塞線程直到系統(tǒng)調(diào)用結(jié)束。假設(shè)隊(duì)列里面所有的goroutine都是長(zhǎng)時(shí)間系統(tǒng)調(diào)用神凑,那么線程都會(huì)被阻塞住净神,這顯然不合理。我們需要的是:goroutine可以阻塞溉委,但線程不可以阻塞或者說(shuō)線程不可以出現(xiàn)長(zhǎng)時(shí)間阻塞鹃唯。要做到這點(diǎn)線程必須知道此時(shí)goroutine的狀態(tài),然后是wait狀態(tài)的時(shí)候就將它換出去瓣喊,執(zhí)行其他goroutine坡慌,同時(shí)記住上次goroutine的上下文,以便下次繼續(xù)執(zhí)行藻三。goroutine的狀態(tài):
這樣G的數(shù)據(jù)結(jié)構(gòu)就出來(lái)了:
struct G ?{
? ? ? uintptr? ? stackguard;? ? // 分段棧的可用空間下界
? ? ?uintptr? ? stackbase;? ? // 分段棧的椇殚伲基址
? ? ?Gobuf? ? ? sched;? ? ? ? //進(jìn)程切換時(shí),利用sched域來(lái)保存上下文
? ? ?uintptr? ? stack0; ?
? ? ?FuncVal*? ? fnstart;? ? // goroutine運(yùn)行的函數(shù)
? ? ?void*? ? param;? ? ? ? ? // 用于傳遞參數(shù)棵帽,睡眠時(shí)其它goroutine設(shè)置param熄求,喚醒時(shí)此goroutine可以獲取
? ? ?int16? ? status;? ? ? ? // 狀態(tài)Gidle,Grunnable,Grunning,Gsyscall,Gwaiting,Gdead
? ? ?int64? ? goid;? ? ? ? ? // goroutine的id號(hào)
? ? ?G*? ? schedlink;
? ? ?M*? ? m;? ? ? // for debuggers, but offset not hard-coded ?
? ? ?M*? ? lockedm;? ? ? ? ? // G被鎖定只能在這個(gè)m上運(yùn)行
? ? ?uintptr? ? gopc;? ? ? ? // 創(chuàng)建這個(gè)goroutine的go表達(dá)式的pc
};
其中status字段就表示goroutine的狀態(tài)。有了這些信息逗概,線程就可以自如地調(diào)度和執(zhí)行協(xié)程了抡四。這個(gè)方案是完美的嗎? 至少我們能看出一個(gè)地方不好:線程每次取G的時(shí)候都要加排它鎖,這樣勢(shì)必導(dǎo)致調(diào)度和執(zhí)行的效率下降。我們把G分成多個(gè)queue指巡,每一個(gè)queue都?xì)w屬于一個(gè)線程去執(zhí)行(M* ? lockedm)淑履。這樣線程只需要管理自己的queue隊(duì)列就好了,不需要每次都競(jìng)爭(zhēng)解決了藻雪。
這樣是不是完美了呢秘噪?牛人說(shuō)過(guò):計(jì)算機(jī)上的所有問(wèn)題都可以通過(guò)增加一個(gè)抽象層來(lái)解決。我們希望線程不要過(guò)多的擔(dān)負(fù)調(diào)度的任務(wù)勉耀,調(diào)度應(yīng)該由抽象層來(lái)完成指煎,線程只管執(zhí)行,于是乎:
M的數(shù)據(jù)結(jié)構(gòu)出來(lái)了:
struct M ? {
? ? ?G*? ? g0;? ? ? ? // 帶有調(diào)度棧的goroutine
? ? ?G*? ? gsignal;? ? // signal-handling G 處理信號(hào)的goroutine
? ? ?void? ? (*mstartfn)(void); ?// 線程入口
? ? ?G*? ? curg;? ? ? ? // M中當(dāng)前運(yùn)行的goroutine
? ? P*? ? p;? ? ? ? // 關(guān)聯(lián)P以執(zhí)行Go代碼 (如果沒(méi)有執(zhí)行Go代碼則P為nil)
? ? P*? ? nextp;
? ? int32? ? mallocing; //狀態(tài)
? ? int32? ? helpgc;? ? ? ? //不為0表示此m在做幫忙gc便斥。helpgc等于n只是一個(gè)編號(hào)
? ? M*? ? alllink;? ? // 這個(gè)域用于鏈接allm
? ? M*? ? schedlink;
? ? ?MCache? ? *mcache;
? ? G*? ? lockedg;
? ? M*? ? nextwaitm;? ? // next M waiting for lock
? ? GCStats? ? gcstats;
};
增加了一個(gè)M層來(lái)參與調(diào)度goroutine至壤,這樣讓線程從調(diào)度和執(zhí)行任務(wù)里面解脫出來(lái)。這下完美了吧J嗑馈O窠帧!=臁镰绎!。假設(shè)一個(gè)情況:有4個(gè)M木西,其中有3個(gè)M在打醬油畴栖,1個(gè)M忙成狗了,這樣好嗎八千?估計(jì)有良心的程序員都覺(jué)得不太好吗讶,那怎么解決這個(gè)問(wèn)題呢?其實(shí)我們需要一個(gè)機(jī)制恋捆,如果發(fā)現(xiàn)空閑了关翎,就從別人家偷點(diǎn)任務(wù)來(lái)玩玩,這好刺激啊鸠信,偷偷摸摸的感覺(jué)纵寝。又需要一個(gè)抽象層來(lái)幫忙下了:
P的數(shù)據(jù)結(jié)構(gòu)出來(lái)了:
struct P ?{
? ? ?Lock;
? ? uint32? ? status;? // Pidle或Prunning等
? ? P*? ? link;
? ? uint32? ? schedtick;? // 每次調(diào)度時(shí)將它加一
? ? M*? ? m;? ? // 鏈接到它關(guān)聯(lián)的M (nil if idle)
? ? MCache*? ? mcache;
? ? G*? ? runq[256];
? ? int32? ? runqhead;
? ? int32? ? runqtail;
? ? G*? ? gfree;
};
早期版本的Golang是沒(méi)有P的,調(diào)度是由G與M完成星立。 這樣的問(wèn)題在于每當(dāng)創(chuàng)建爽茴、終止Goroutine或者需要調(diào)度時(shí),需要一個(gè)全局的鎖來(lái)保護(hù)調(diào)度的相關(guān)對(duì)象(sched)绰垂。 全局鎖嚴(yán)重影響Goroutine的并發(fā)性能室奏。 (Scalable Go Scheduler)。 通過(guò)引入P劲装,實(shí)現(xiàn)了一種叫做work-stealing的調(diào)度算法:
1. 每個(gè)P維護(hù)一個(gè)G隊(duì)列胧沫;
2. 當(dāng)一個(gè)G被創(chuàng)建出來(lái)昌简,或者變?yōu)榭蓤?zhí)行狀態(tài)時(shí),就把他放到P的可執(zhí)行隊(duì)列中绒怨;
3. 當(dāng)一個(gè)G執(zhí)行結(jié)束時(shí)纯赎,P會(huì)從隊(duì)列中把該G取出;如果此時(shí)P的隊(duì)列為空南蹂,即沒(méi)有其他G可以執(zhí)行犬金, 就隨機(jī)選擇另外一個(gè)P,從其可執(zhí)行的G隊(duì)列中偷取一半六剥。
該算法避免了在Goroutine調(diào)度時(shí)使用全局鎖晚顷。
恩,這下完美了疗疟?至少Rob Pike覺(jué)得這樣完美了该默。講到現(xiàn)在一直沒(méi)有說(shuō)其中還有一個(gè)隱形的手,這個(gè)手就是Schedu調(diào)度器策彤,它是幕后的大Boss栓袖。