溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Go routine調(diào)度詳解

發(fā)布時(shí)間:2020-08-28 14:48:43 來(lái)源:腳本之家 閱讀:150 作者:曾紀(jì)文 欄目:編程語(yǔ)言

goroutine簡(jiǎn)介

goroutine是go語(yǔ)言中最為NB的設(shè)計(jì),也是其魅力所在,goroutine的本質(zhì)是協(xié)程,是實(shí)現(xiàn)并行計(jì)算的核心。goroutine使用方式非常的簡(jiǎn)單,只需使用go關(guān)鍵字即可啟動(dòng)一個(gè)協(xié)程,并且它是處于異步方式運(yùn)行,你不需要等它運(yùn)行完成以后在執(zhí)行以后的代碼。

go func()//通過(guò)go關(guān)鍵字啟動(dòng)一個(gè)協(xié)程來(lái)運(yùn)行函數(shù)

go routine的調(diào)度原理和操作系統(tǒng)的線(xiàn)層調(diào)度是比較相似的。這里我們將介紹go routine的相關(guān)知識(shí)。

goroutine(有人也稱(chēng)之為協(xié)程)本質(zhì)上go的用戶(hù)級(jí)線(xiàn)程的實(shí)現(xiàn),這種用戶(hù)級(jí)線(xiàn)程是運(yùn)行在內(nèi)核級(jí)線(xiàn)程之上。當(dāng)我們?cè)趃o程序中創(chuàng)建goroutine的時(shí)候,我們的這些routine將會(huì)被分配到不同的內(nèi)核級(jí)線(xiàn)程中運(yùn)行。一個(gè)內(nèi)核級(jí)線(xiàn)程可能會(huì)負(fù)責(zé)多個(gè)routine的運(yùn)行。而保證這些routine在內(nèi)內(nèi)核級(jí)線(xiàn)程安全、公平、高效運(yùn)行的工作,就由調(diào)度器來(lái)實(shí)現(xiàn)。

goroutine內(nèi)部原理

概念介紹

在進(jìn)行實(shí)現(xiàn)原理之前,了解下一些關(guān)鍵性術(shù)語(yǔ)的概念。

并發(fā)

一個(gè)cpu上能同時(shí)執(zhí)行多項(xiàng)任務(wù),在很短時(shí)間內(nèi),cpu來(lái)回切換任務(wù)執(zhí)行(在某段很短時(shí)間內(nèi)執(zhí)行程序a,然后又迅速得切換到程序b去執(zhí)行),有時(shí)間上的重疊(宏觀上是同時(shí)的,微觀仍是順序執(zhí)行),這樣看起來(lái)多個(gè)任務(wù)像是同時(shí)執(zhí)行,這就是并發(fā)。

并行

當(dāng)系統(tǒng)有多個(gè)CPU時(shí),每個(gè)CPU同一時(shí)刻都運(yùn)行任務(wù),互不搶占自己所在的CPU資源,同時(shí)進(jìn)行,稱(chēng)為并行。

進(jìn)程

cpu在切換程序的時(shí)候,如果不保存上一個(gè)程序的狀態(tài)(也就是我們常說(shuō)的context--上下文),直接切換下一個(gè)程序,就會(huì)丟失上一個(gè)程序的一系列狀態(tài),于是引入了進(jìn)程這個(gè)概念,用以劃分好程序運(yùn)行時(shí)所需要的資源。因此進(jìn)程就是一個(gè)程序運(yùn)行時(shí)候的所需要的基本資源單位(也可以說(shuō)是程序運(yùn)行的一個(gè)實(shí)體)。

線(xiàn)程

cpu切換多個(gè)進(jìn)程的時(shí)候,會(huì)花費(fèi)不少的時(shí)間,因?yàn)榍袚Q進(jìn)程需要切換到內(nèi)核態(tài),而每次調(diào)度需要內(nèi)核態(tài)都需要讀取用戶(hù)態(tài)的數(shù)據(jù),進(jìn)程一旦多起來(lái),cpu調(diào)度會(huì)消耗一大堆資源,因此引入了線(xiàn)程的概念,線(xiàn)程本身幾乎不占有資源,他們共享進(jìn)程里的資源,內(nèi)核調(diào)度起來(lái)不會(huì)那么像進(jìn)程切換那么耗費(fèi)資源。

協(xié)程

協(xié)程擁有自己的寄存器上下文和棧。協(xié)程調(diào)度切換時(shí),將寄存器上下文和棧保存到其他地方,在切回來(lái)的時(shí)候,恢復(fù)先前保存的寄存器上下文和棧。因此,協(xié)程能保留上一次調(diào)用時(shí)的狀態(tài)(即所有局部狀態(tài)的一個(gè)特定組合),每次過(guò)程重入時(shí),就相當(dāng)于進(jìn)入上一次調(diào)用的狀態(tài),換種說(shuō)法:進(jìn)入上一次離開(kāi)時(shí)所處邏輯流的位置。線(xiàn)程和進(jìn)程的操作是由程序觸發(fā)系統(tǒng)接口,最后的執(zhí)行者是系統(tǒng);協(xié)程的操作執(zhí)行者則是用戶(hù)自身程序,goroutine也是協(xié)程。

Go調(diào)度的組成

Go的調(diào)度主要有四個(gè)結(jié)構(gòu)組成,分別是:

  • G:goroutine的核心結(jié)構(gòu),包括routine的棧、程序計(jì)數(shù)器pc、以及一些狀態(tài)信息等;
  • M:內(nèi)核級(jí)線(xiàn)程。goroutine在M上運(yùn)行。M中信息包括:正在運(yùn)行的goroutine、等待運(yùn)行的routine列表等。當(dāng)然也包括操作系統(tǒng)線(xiàn)程相關(guān)信息,這些此處不討論。
  • P:processor,處理器,只要用于執(zhí)行g(shù)oroutine,維護(hù)了一個(gè)goroutine列表。其實(shí)P是可以從屬于M的。當(dāng)P從屬于(分配給)M的時(shí)候,表示P中的某個(gè)goroutine得以運(yùn)行。當(dāng)P不從屬于M的時(shí)候,表示P中的所有g(shù)oroutine都需要等待被安排到內(nèi)核級(jí)線(xiàn)程運(yùn)行。
  • Sched:調(diào)度器,存儲(chǔ)、維護(hù)M,以及一個(gè)全局的goroutine等待隊(duì)列,以及其他狀態(tài)信息。

Go程序的啟動(dòng)過(guò)程

  • 初始化Sched:一個(gè)存儲(chǔ)P的列表pidle。P的數(shù)量可以通過(guò)GOMAXPROCS設(shè)置;
  • 創(chuàng)建第一個(gè)goroutine。這個(gè)goroutine會(huì)創(chuàng)建一個(gè)M,這個(gè)內(nèi)核級(jí)線(xiàn)程(sysmon)的工作是對(duì)goroutine進(jìn)行監(jiān)控。之后,這個(gè)goroutine開(kāi)始我們?cè)趍ain函數(shù)里面的代碼,此時(shí),該goroutine就是我們說(shuō)的主routine。

創(chuàng)建goroutine:

  • goroutine創(chuàng)建時(shí)指定了代碼段
  • 然后,goroutine被加入到P中去等待運(yùn)行。
  • 這個(gè)新建的goroutine的信息包含:棧地址、程序計(jì)數(shù)器

創(chuàng)建內(nèi)核級(jí)線(xiàn)程M

內(nèi)核級(jí)線(xiàn)程由go的運(yùn)行時(shí)根據(jù)實(shí)際情況創(chuàng)建,我們無(wú)法再go中創(chuàng)建內(nèi)核級(jí)線(xiàn)程。那什么時(shí)候回創(chuàng)建內(nèi)核級(jí)線(xiàn)程呢?當(dāng)前程序等待運(yùn)行的goroutine數(shù)量達(dá)到一定數(shù)量及存在空閑(為被分配給M)的P的時(shí)候,Go運(yùn)行時(shí)就會(huì)創(chuàng)建一些M,然后將空閑的P分配給新建的內(nèi)核級(jí)線(xiàn)程M,接著才是獲取、運(yùn)行g(shù)oroutine。創(chuàng)建M的接口函數(shù)如下:

// 創(chuàng)建M的接口函數(shù)
void newm(void (*fn)(void), P *p)

// 分配P給M
if(m != &runtime·m0) {Â
  acquirep(m->nextp);
  m->nextp = nil;
}
// 獲取goroutine并開(kāi)始運(yùn)行
schedule();

M的運(yùn)行

static void schedule(void)
{
  G *gp;

  gp = runqget(m->p);
  if(gp == nil)
    gp = findrunnable();

 // 如果P的類(lèi)別不止一個(gè)goroutine,且調(diào)度器中有空閑的的P,就喚醒其他內(nèi)核級(jí)線(xiàn)程M
  if (m->p->runqhead != m->p->runqtail &&
    runtime·atomicload(&runtime·sched.nmspinning) == 0 &&
    runtime·atomicload(&runtime·sched.npidle) > 0) // TODO: fast atomic
    wakep();
 // 執(zhí)行g(shù)oroutine
  execute(gp);
}

  • runqget: 從P中獲取goroutine即gp。gp可能為nil(如M剛創(chuàng)建時(shí)P為空;或者P的goroutine已經(jīng)運(yùn)行完了)。
  • findrunnable:尋找空閑的goroutine(從全局的goroutine等待隊(duì)列獲取goroutine;如果所有g(shù)oroutine都已經(jīng)被分配了,那么從其他M的P的goroutine的goroutine列表獲取一些)。如果獲取到goroutine,就將他放入P中,并執(zhí)行它;否則沒(méi)能獲取到任何的goroutine,該內(nèi)核級(jí)線(xiàn)程進(jìn)行系統(tǒng)調(diào)用sleep了。
  • wakep:當(dāng)當(dāng)前內(nèi)核級(jí)線(xiàn)程M的P中不止一個(gè)goroutine且調(diào)度器中有空閑的的P,就喚醒其他內(nèi)核級(jí)線(xiàn)程M。(為了找些空閑的M幫自己分擔(dān))。

Routine狀態(tài)遷移

前面說(shuō)的是G,M是怎樣創(chuàng)建的以及什么時(shí)候創(chuàng)建、運(yùn)行。那么goroutine在M是是怎樣進(jìn)行調(diào)度的呢?這個(gè)才是goroutine的調(diào)度核心問(wèn)題,即上面代碼中的schedule。在說(shuō)調(diào)度之前,我們必須知道goroutine的狀態(tài)有什么,以及各個(gè)狀態(tài)之間的關(guān)系。

Go routine調(diào)度詳解

  • Gidle:創(chuàng)建中的goroutine,實(shí)際上這個(gè)狀態(tài)沒(méi)有什么用;
  • Grunnable:新創(chuàng)建完成的goroutine在完成了資源的分配及初始化后,會(huì)進(jìn)入這個(gè)狀態(tài)。這個(gè)新創(chuàng)建的goroutine會(huì)被分配到創(chuàng)建它的M的P中;
  • Grunning:當(dāng)Grunnable中的goroutine等到了空閑的cpu或者到了自己的時(shí)間片的時(shí)候,就會(huì)進(jìn)入Grunning狀態(tài)。這個(gè)裝下的goroutine可以被前文提到的findrunnable函數(shù)獲?。?/li>
  • Gwaiting:當(dāng)正在運(yùn)行的goroutine進(jìn)行一些阻塞調(diào)用的時(shí)候,就會(huì)從Grunning狀態(tài)進(jìn)入Gwaiting狀態(tài)。常見(jiàn)的調(diào)用有:寫(xiě)入一個(gè)滿(mǎn)的channel、讀取空的channel、IO操作、定時(shí)器Ticker等。當(dāng)阻塞調(diào)用完成后,goroutine的狀態(tài)就會(huì)從Gwaiting轉(zhuǎn)變?yōu)镚runnable;
  • Gsyscall:當(dāng)正在運(yùn)行的goroutine進(jìn)行系統(tǒng)調(diào)用的時(shí)候,其狀態(tài)就會(huì)轉(zhuǎn)變?yōu)镚syscall。當(dāng)系統(tǒng)調(diào)用完成后goroutine的狀態(tài)就會(huì)變?yōu)镚runnable。(前文提到的sysmon進(jìn)程會(huì)監(jiān)控所有的P,如果發(fā)現(xiàn)有的P的系統(tǒng)調(diào)用是阻塞式的或者執(zhí)行的時(shí)間過(guò)長(zhǎng),就會(huì)將P從原來(lái)的M分離出來(lái),并新建一個(gè)M,將P分配給這個(gè)新建的M)。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI