溫馨提示×

溫馨提示×

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

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

Go語言中g(shù)oroutine的調(diào)度原理是什么

發(fā)布時間:2021-07-06 16:31:41 來源:億速云 閱讀:159 作者:Leah 欄目:數(shù)據(jù)庫

Go語言中g(shù)oroutine的調(diào)度原理是什么,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。

一、關(guān)于并發(fā)的基礎(chǔ)知識

在講goroutine的調(diào)度原理之前,有些與操作系統(tǒng)相關(guān)的知識,我們需要先知道,例如:

1.什么是并發(fā)?

并發(fā):兩個或兩個以上的任務(wù)在一段時間內(nèi)被執(zhí)行。我們并不關(guān)心這些任務(wù)是否在同一時刻執(zhí)行,我們只是知道,這些任務(wù)在這一段時間能能夠都被執(zhí)行,當(dāng)然這一段時間可以很長,也可以很短。

2.并發(fā)的最小并發(fā)單位是什么?

進(jìn)程是計(jì)算機(jī)資源分配最小的單位,是CPU分配資源的基本單位,具有獨(dú)立的內(nèi)存。

線程是計(jì)算機(jī)調(diào)度最小的單位,也是程序執(zhí)行的最小單位,是在進(jìn)程中的,一個進(jìn)程往往會有一個到多個線程。

3.計(jì)算機(jī)是如何實(shí)現(xiàn)并發(fā)的?

計(jì)算機(jī)的分時調(diào)用是并發(fā)的根本,CPU通過快速的切換作業(yè)來執(zhí)行不同的作業(yè),基本的調(diào)度單位在執(zhí)行的時候可以被阻塞掉,此時就會將CPU資源讓出來,等到該調(diào)度單位再次被喚醒的時候,又可以使用CPU資源,而操作系統(tǒng)保證了整個的調(diào)度過程。

二、Goroutine的基礎(chǔ)知識

除此之外,關(guān)于goroutine的調(diào)度原理,我們需要弄清楚下面幾個問題。

1.goroutine是什么?

Goroutine:是Go里的一種輕量級線程——協(xié)程。

1)相對線程,協(xié)程的優(yōu)勢就在于它非常輕量級,進(jìn)行上下文切換的代價(jià)非常的小。

2)對于一個goroutine ,每個結(jié)構(gòu)體G中有一個sched的屬性就是用來保存它上下文的。這樣,goroutine 就可以很輕易的來回切換。

3)由于其上下文切換在用戶態(tài)下發(fā)生,根本不必進(jìn)入內(nèi)核態(tài),所以速度很快。而且只有當(dāng)前goroutine 的 PC, SP等少量信息需要保存。

4)在Go語言中,每一個并發(fā)的執(zhí)行單元為一個goroutine。

Go 語言中的goroutine并發(fā), 采用的是CSP(communicating sequential processes)并發(fā)模型,講究的是以通訊的方式來進(jìn)行數(shù)據(jù)共享,是通過goroutine配合channel的方式來實(shí)現(xiàn)的。(備注:這部分知識后續(xù)單獨(dú)整理一章。)

2.既然它是比線程還小的粒度,那么它與線程有什么關(guān)系?

Go語言的線程模型就是一種特殊的兩級線程模型,如下所示:

兩級線程模型的實(shí)現(xiàn)非常復(fù)雜,和內(nèi)核級線程模型類似,一個進(jìn)程中可以對應(yīng)多個內(nèi)核級線程,但是進(jìn)程中的線程不和內(nèi)核線程一一對應(yīng);這種線程模型會先創(chuàng)建多個內(nèi)核級線程,然后用自身的用戶級線程去對應(yīng)創(chuàng)建的多個內(nèi)核級線程,自身的用戶級線程需要本身程序去調(diào)度,內(nèi)核級的線程交給操作系統(tǒng)內(nèi)核去調(diào)度。

三、Goroutine的調(diào)度策略

我們先來看下,Go線程實(shí)現(xiàn)了MPG模型:

S(Sched):結(jié)構(gòu)就是調(diào)度器,它維護(hù)有存儲M和G的隊(duì)列以及調(diào)度器的一些狀態(tài)信息等。

M(Machine):一個M直接關(guān)聯(lián)了一個內(nèi)核線程。

P(processor):代表了M所需的上下文環(huán)境,也是處理用戶級代碼邏輯的處理器。G(Goroutine):其實(shí)本質(zhì)上也是一種輕量級的線程。

它們的關(guān)系如下所示:

介紹:

一個M會關(guān)聯(lián)兩個東西,一個是內(nèi)核線程,一個是可執(zhí)行的進(jìn)程。

一個上下文P會有兩類Goroutine,一類是正在運(yùn)行的,圖中的藍(lán)色G;一類是正在排隊(duì)的,圖中灰色G,這個會存儲在該進(jìn)程中的runqueue里面。

這里的上下文P的數(shù)量也表示的是Goroutinue運(yùn)行的數(shù)量,一般設(shè)置為幾個,機(jī)器中就會并發(fā)運(yùn)行幾個。當(dāng)然這里P的數(shù)量是可以設(shè)置的,通過環(huán)境變量GOMAXPROCS的值,或者通過運(yùn)行時調(diào)用函數(shù)runtime.GOMAXPROCS()進(jìn)行設(shè)置,最大值是256。

有了上面的知識,我們知道了Goroutine的一些基本概念,但是我們還是不知道,Go的并發(fā)是如何調(diào)度的。而這一個話題,就需要我們將Goroutine的幾種場景(創(chuàng)建、銷毀和運(yùn)行)做拆分。

1.在執(zhí)行g(shù)o語句之前,我們看下程序都做了哪些準(zhǔn)備,也就是程序的初始化啟動流程是什么樣子的?

上面的代碼,有三個點(diǎn)非常關(guān)鍵,分別是runtime.schedinit,runtime.main,runtime.mstart

Step1: runtime.schedinit:這一步是調(diào)度器的初始化操作,它會設(shè)置GOMAXPROCS的大小,這里的大小不能超過它的上限256,并創(chuàng)建設(shè)置好對應(yīng)數(shù)量的P,當(dāng)然這些P都處于閑置狀態(tài);然后,將這些創(chuàng)建好的P都存放到sched中pidle所關(guān)聯(lián)的閑置列表中。

Step2: 程序會繼續(xù)執(zhí)行runtime.newproc來創(chuàng)建程序的第一個goroutine,而這個goroutine會執(zhí)行runtime.main也就是我們看到的main函數(shù),在這之后main會主動創(chuàng)建一個內(nèi)核線程M,這個M只用來做系統(tǒng)監(jiān)控用,這個內(nèi)核線程與程序中g(shù)oroutinue的調(diào)度有關(guān)系。

Step3:在runtime.mstart之后,程序就開始執(zhí)行了,如果后續(xù)需要創(chuàng)建goroutine,就會調(diào)用go語句來創(chuàng)建。

2.goroutine創(chuàng)建流程是什么樣子的?

在調(diào)用go func()的時候,會調(diào)用runtime.newproc來創(chuàng)建一個goroutine,這個goroutine會新建一個自己的??臻g,同時在G的sched中維護(hù)棧地址與程序計(jì)數(shù)器這些信息(備注:這些數(shù)據(jù)在goroutine被調(diào)度的時候會被用到。準(zhǔn)確的說該goroutine在放棄cpu之后,下一次在重新獲取cpu的時候,這些信息會被重新加載到cpu的寄存器中。)

創(chuàng)建好的這個goroutine會被放到,它所對應(yīng)的內(nèi)核線程M所使用的上下文P中的runqueue中。等待調(diào)度器來決定何時取出該goroutine并執(zhí)行,通常調(diào)度是按時間順序被調(diào)度的,這個隊(duì)列是一個先進(jìn)先出的隊(duì)列。

3.新建的這些goroutine是如何被調(diào)度的呢?

goroutine在創(chuàng)建好了之后,調(diào)度器會決定何時執(zhí)行這個goroutine,這個過程就叫做調(diào)度。

新建好的goroutine,最開始都會存儲在某一個線程M,所關(guān)聯(lián)的上下文P的runqueue中,但是在后續(xù)的調(diào)度中,有些goroutine因?yàn)檎{(diào)用了runtime.gosched,會被放到全局隊(duì)列中。

線程M的選擇過程,按照下面的順序執(zhí)行:

1.從M對應(yīng)的P中的runqueue中取出goroutine,來執(zhí)行,沒有的話,執(zhí)行2。

2.從全局隊(duì)列里面嘗試取出一個goroutine來執(zhí)行,有的話,執(zhí)行!沒有的話,執(zhí)行3。

3.從其他的線程M的P中,偷出一些goroutine來執(zhí)行,偷失敗了,執(zhí)行4。(備注:這里偷的話,一偷就偷一半,使用的算法叫做work stealing。)

4.線程M發(fā)現(xiàn)無事可做,就去休息了,也就是線程的sleep,它等待被喚醒。

4.運(yùn)行中的goroutine是怎么停止的呢?一旦被停止了的話,那排隊(duì)在它后面的goutinue該怎么辦?

講完了goroutine的調(diào)度之后,我們便要考慮一個問題,正在被執(zhí)行的goroutine何時停止,停止了之后會發(fā)生什么?而掛在M對應(yīng)的P后面的runqueue中的goroutine該怎么辦?

情況1:runtime·park

當(dāng)調(diào)用了runtime·park函數(shù)之后,goroutine會被設(shè)置成waiting狀態(tài),線程M會放棄它自身關(guān)聯(lián)的上下文P,而系統(tǒng)會分配一個新的線程M1來接管這個上下文P,(備注:當(dāng)然這里面的M1也有可能是本來就創(chuàng)建好的,處于閑置狀態(tài)中的)。

原來的線程M0則會與上下文斷開連接,M0因?yàn)闊o事可做,就去sleep了,等待下次被喚醒。如下圖所示:

channel的讀寫操作,定時器中,網(wǎng)絡(luò)poll等都有可能park goroutine。

情況2:runtime·gosched

調(diào)用runtime·gosched函數(shù)也可以讓當(dāng)前goroutine放棄cpu,這種情況下會將goroutine設(shè)置成runnable,放置到全局隊(duì)列中。備注:這個也就是為什么全局變量的queue里面會有g(shù)oroutine的原因。

5.goroutine被喚醒之后,會做什么?

goroutine處于waiting狀態(tài)的話,在調(diào)用runtime·ready函數(shù)之后,會被喚醒,喚醒的goroutine會被重新放到,M對應(yīng)的上下文所對應(yīng)的runqueue中,等待被調(diào)度。

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。

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

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

AI