成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

我所理解的 Go 的 GPM 模型

開發(fā) 前端 人工智能
理解 ??main?? 函數(shù)本身也是一個(gè) goroutine 有助于更好地認(rèn)識(shí) Go 的并發(fā)模型的一致性:所有用戶代碼都運(yùn)行在 goroutine 之上,由統(tǒng)一的 GPM 模型進(jìn)行調(diào)度和管理。這體現(xiàn)了 Go 在語(yǔ)言層面和運(yùn)行時(shí)層面將并發(fā)作為一等公民的設(shè)計(jì)哲學(xué)。

Go 語(yǔ)言(Golang)的一大顯著特性是在其語(yǔ)法層面就內(nèi)建了對(duì)協(xié)程,即 goroutine 的支持,并且其運(yùn)行時(shí)(runtime)系統(tǒng)為這一功能提供了強(qiáng)大且原生的支撐。在我看來(lái),選擇使用協(xié)程而非傳統(tǒng)的線程來(lái)支持高并發(fā)任務(wù),帶來(lái)了諸多益處:

  • 切換成本更低 :協(xié)程的切換是純用戶態(tài)的操作,由 Go runtime 直接控制,避免了線程切換時(shí)需要在內(nèi)核態(tài)和用戶態(tài)之間傳遞上下文信息的開銷。相比之下,線程切換由操作系統(tǒng)(OS)層面實(shí)現(xiàn),成本更高。
  • 調(diào)度靈活性 :goroutine 的調(diào)度由 Go runtime 決定,而非操作系統(tǒng)。這使得 Go 可以根據(jù)應(yīng)用特性實(shí)現(xiàn)更優(yōu)化的調(diào)度策略。
  • 支持大規(guī)模并發(fā) :由于協(xié)程在用戶態(tài)實(shí)現(xiàn)且資源占用小(例如,初始棧空間通常只有幾 KB),因此可以輕松創(chuàng)建和管理成千上萬(wàn)甚至數(shù)百萬(wàn)的 goroutine,遠(yuǎn)超傳統(tǒng)線程所能支持的并發(fā)量。
  • 創(chuàng)建與銷毀成本低 :goroutine 的創(chuàng)建和銷毀由 Go runtime 管理,其開銷遠(yuǎn)小于操作系統(tǒng)線程。它們的棧空間是動(dòng)態(tài)伸縮的,初始分配很小,按需增長(zhǎng),回收也更高效。
  • 簡(jiǎn)化的并發(fā)編程模型 :通過(guò) channel 和 select 等機(jī)制,goroutine 使得編寫和理解并發(fā)邏輯更為簡(jiǎn)單和安全,減少了對(duì)傳統(tǒng)并發(fā)編程中復(fù)雜鎖機(jī)制的依賴。

然而,這些輕量級(jí)的 goroutine 終究需要依托實(shí)際的操作系統(tǒng)線程才能在 CPU 上執(zhí)行。Go 語(yǔ)言是如何高效管理這些 goroutine 的呢?這就引出了我們今天要深入探討的核心機(jī)制—— GPM 模型 。

總體談?wù)?GPM

GPM 是 Go 調(diào)度器中三個(gè)核心組件的縮寫:

  • G (Goroutine) :即 Go 協(xié)程。它是 Go 程序中并發(fā)執(zhí)行的基本單元,擁有自己的棧空間、指令指針以及其他用于調(diào)度和執(zhí)行的上下文信息。G 的數(shù)量可以非常龐大。
  • P (Processor) :邏輯處理器。P 并非指物理 CPU 核心,而是 Go runtime 中的一個(gè)概念,它代表了 M (內(nèi)核線程) 執(zhí)行 Go 代碼所需要的上下文和資源,例如本地可運(yùn)行 G 的隊(duì)列(Local Run Queue, LRQ)、內(nèi)存分配狀態(tài)等。每個(gè) P 同時(shí)只能運(yùn)行一個(gè) G。P 的數(shù)量通常由環(huán)境變量 GOMAXPROCS 決定,默認(rèn)情況下等于可用的 CPU 核心數(shù)。
  • M (Machine) :內(nèi)核線程,即操作系統(tǒng)管理的線程。M 是實(shí)際執(zhí)行 Go 代碼的實(shí)體。一個(gè) M 必須與一個(gè) P 關(guān)聯(lián)才能執(zhí)行 G。

我們首先從宏觀層面理解這種設(shè)計(jì)背后的考量:

通過(guò)設(shè)定 GOMAXPROCS 來(lái)控制 P 的數(shù)量,Go 程序既能確保充分利用多核 CPU 的計(jì)算能力,又避免了因過(guò)多線程競(jìng)爭(zhēng) CPU 資源而導(dǎo)致的性能下降。通常,P 的數(shù)量與 CPU 核心數(shù)相等,這意味著在理想情況下,每個(gè)核心都有一個(gè) P 在積極地調(diào)度和執(zhí)行 G。

P 的角色至關(guān)重要,它作為 G 和 M 之間的橋梁。P 持有一個(gè)本地可運(yùn)行 G 的隊(duì)列 (LRQ),當(dāng) M 需要執(zhí)行任務(wù)時(shí),它會(huì)從其關(guān)聯(lián)的 P 的 LRQ 中獲取 G 來(lái)執(zhí)行。這種設(shè)計(jì)使得 G 的調(diào)度大部分發(fā)生在用戶態(tài),避免了頻繁的內(nèi)核態(tài)切換。

此外,P 與 M 的結(jié)合實(shí)現(xiàn)了線程的復(fù)用。當(dāng)一個(gè) M 因?yàn)閳?zhí)行的 G 進(jìn)行了阻塞性的系統(tǒng)調(diào)用(syscall)而被阻塞時(shí),它所關(guān)聯(lián)的 P 可以被釋放,并被另一個(gè)空閑的 M 或者一個(gè)新創(chuàng)建的 M 獲取,從而繼續(xù)執(zhí)行 P 本地隊(duì)列中的其他 G。這樣就避免了因?yàn)樯贁?shù)阻塞操作導(dǎo)致大量線程閑置,同時(shí)也減少了線程頻繁創(chuàng)建和銷毀的開銷,相當(dāng)于 Go runtime 內(nèi)部實(shí)現(xiàn)了一個(gè)高效的線程池。這一切對(duì)編寫 Go 代碼的用戶來(lái)說(shuō)是透明的。

GPM 是如何調(diào)度的?

要理解 GPM 的調(diào)度機(jī)制,首先需要了解幾個(gè)關(guān)鍵的概念和數(shù)據(jù)結(jié)構(gòu):

  • 全局運(yùn)行隊(duì)列 (Global Run Queue, GRQ) :當(dāng) P 的本地運(yùn)行隊(duì)列沒(méi)有空間,或者某些 G(例如從網(wǎng)絡(luò)調(diào)用返回的 G、被搶占的 G)被喚醒或需要重新調(diào)度時(shí),它們可能會(huì)被放入全局運(yùn)行隊(duì)列。
  • P 的本地運(yùn)行隊(duì)列 (Local Run Queue, LRQ) :每個(gè) P 都有一個(gè)自己的 LRQ,用于存放待在該 P 上執(zhí)行的 G。M 會(huì)優(yōu)先從其關(guān)聯(lián) P 的 LRQ 中獲取 G。LRQ 的存在減少了對(duì) GRQ 的競(jìng)爭(zhēng),提高了調(diào)度效率。
  • g0 :每個(gè) M 都有一個(gè)特殊的 goroutine,稱為 g0g0 擁有自己的棧空間(獨(dú)立于用戶 G 的棧,通常較大),主要用于執(zhí)行調(diào)度相關(guān)的代碼、垃圾回收的輔助工作以及其他運(yùn)行時(shí)任務(wù)。當(dāng) M 需要切換到某個(gè)用戶 G 執(zhí)行時(shí),會(huì)從 g0 棧切換到用戶 G 的棧;反之亦然。
  • m.curg :指向當(dāng)前在 M 上運(yùn)行的用戶 G。
  • G 的狀態(tài) :Goroutine 在其生命周期中會(huì)經(jīng)歷多種狀態(tài),例如 _Gidle(閑置,剛被分配還未使用)、_Grunnable(可運(yùn)行,在運(yùn)行隊(duì)列中等待調(diào)度)、_Grunning(運(yùn)行中,正在 M 上執(zhí)行)、_Gsyscall(進(jìn)行系統(tǒng)調(diào)用,M 已與 P 分離)、_Gwaiting(等待中,如等待 channel 操作、鎖、或定時(shí)器)、_Gdead(已結(jié)束,資源可回收)、_Gcopystack(棧復(fù)制中,通常在棧增長(zhǎng)時(shí)發(fā)生)、_Gpreempted(被搶占,等待重新調(diào)度)。
  • P 的狀態(tài) :P 也有不同的狀態(tài),如 _Pidle(閑置,沒(méi)有 M 與之關(guān)聯(lián)或沒(méi)有可運(yùn)行的 G)、_Prunning(運(yùn)行中,有 M 與之關(guān)聯(lián)并正在執(zhí)行 G 或調(diào)度代碼)、_Psyscall(其關(guān)聯(lián)的 M 正在進(jìn)行一個(gè)阻塞的系統(tǒng)調(diào)用,P 本身可能被其他 M 使用)、_Pgcstop(因垃圾回收而停止)、_Pdead(不再使用,例如 GOMAXPROCS 被調(diào)小時(shí))。

調(diào)度決策在很大程度上是每個(gè) M 各自獨(dú)立在其 g0 棧上執(zhí)行的。當(dāng)一個(gè) M 空閑下來(lái)(例如,其當(dāng)前 G 執(zhí)行完畢或被阻塞),它會(huì)運(yùn)行調(diào)度代碼來(lái)尋找下一個(gè)可運(yùn)行的 G。

  • 沒(méi)有單一的“總控”M :Go 的調(diào)度器設(shè)計(jì)上是去中心化的,沒(méi)有一個(gè)特定的 M 作為“總控制器”來(lái)指揮所有其他 M。這種設(shè)計(jì)避免了單點(diǎn)瓶頸,提高了并發(fā)度。
  • 協(xié)調(diào)機(jī)制 :盡管調(diào)度是分布式的,但 M 之間通過(guò)一些共享結(jié)構(gòu)和機(jī)制進(jìn)行協(xié)調(diào):

a.全局運(yùn)行隊(duì)列 (GRQ) :為所有 P 提供了一個(gè)共享的 G 來(lái)源。

b.工作竊取 (Work Stealing) :空閑的 M 會(huì)嘗試從其他 P 的 LRQ 中“竊取”任務(wù)。

c.sysmon 后臺(tái)監(jiān)控線程 :這是一個(gè)特殊的 M(不與 P 綁定),它負(fù)責(zé)一些全局性的協(xié)調(diào)任務(wù),比如垃圾回收的觸發(fā)和輔助、網(wǎng)絡(luò)輪詢器(Netpoller)事件的處理(間接影響調(diào)度,通過(guò)將等待 I/O 的 G 變?yōu)榭蛇\(yùn)行狀態(tài))、以及檢測(cè)并搶占長(zhǎng)時(shí)間運(yùn)行的 G。sysmon 更像是一個(gè)維護(hù)者和協(xié)調(diào)者,而非一個(gè)命令下發(fā)者。

d.P 的管理 :Go runtime 負(fù)責(zé)管理 P 的池。當(dāng) M 因系統(tǒng)調(diào)用阻塞時(shí)釋放 P,或當(dāng)有空閑 P 和可運(yùn)行 G 時(shí),runtime 會(huì)嘗試喚醒或創(chuàng)建 M 來(lái)綁定這些 P。

  • 這種分布式的調(diào)度配合全局協(xié)調(diào)機(jī)制,使得 Go 的調(diào)度器既高效又具有良好的伸縮性。

接下來(lái),我們通過(guò)幾個(gè)例子來(lái)具體闡述調(diào)度過(guò)程:

1. 基本調(diào)度流程

假設(shè)我們有一個(gè) P0 和一個(gè) M0,P0 的 LRQ 中有 G1。

  • 獲取 G :M0 啟動(dòng)后,或者當(dāng)它完成了前一個(gè) G 的執(zhí)行后,它會(huì)首先查看其關(guān)聯(lián)的 P0 的 LRQ。此時(shí),M0 在 g0 棧上執(zhí)行調(diào)度邏輯。
  • 執(zhí)行 G :M0 從 P0 的 LRQ 中取出 G1。G1 的狀態(tài)從 _Grunnable 變?yōu)?nbsp;_Grunning,P0 的狀態(tài)保持或變?yōu)?nbsp;_Prunning。M0 的 m.curg 指向 G1。隨后,M0 從 g0 棧切換到 G1 的棧,開始執(zhí)行 G1 的代碼。
  • G 執(zhí)行完畢 :當(dāng) G1 執(zhí)行完畢(例如函數(shù)返回),它會(huì)切換回 g0 棧。G1 的狀態(tài)變?yōu)?nbsp;_Gdead,其資源會(huì)被回收。M0 (在 g0 棧上) 接著會(huì)再次嘗試從 P0 的 LRQ 尋找下一個(gè)可運(yùn)行的 G。
  • LRQ 為空 :如果 P0 的 LRQ 為空,M0(在 g0 棧上)會(huì)嘗試進(jìn)行 工作竊取 (work-stealing) ,它會(huì)隨機(jī)查看其他 P 的 LRQ,如果發(fā)現(xiàn)有 G,就竊取一半過(guò)來(lái)放到自己的 P0 的 LRQ 中。如果其他 P 的 LRQ 也都為空,M0 會(huì)嘗試從 GRQ 獲取 G。
  • GRQ 也為空 :如果 GRQ 也為空,M0 可能會(huì)將 P0 置為 _Pidle 狀態(tài),并解除 M0 與 P0 的關(guān)聯(lián),M0 自身也可能進(jìn)入休眠(park)狀態(tài),等待新的 G 到來(lái)時(shí)被喚醒。或者,M0 會(huì)去自旋(spinning)一段時(shí)間,期望短期內(nèi)有新的 G 產(chǎn)生。

自旋 (spinning) 是指 M 在一個(gè)緊密的循環(huán)中不斷檢查是否有可運(yùn)行的 G,而不立即放棄 CPU。

  • CPU 占用 :在自旋期間,M 會(huì)持續(xù)消耗 CPU 資源,如果該 CPU 核心上沒(méi)有其他更高優(yōu)先級(jí)的任務(wù),它可能會(huì)達(dá)到 100% 的占用率。
  • 為何自旋 :這是一種以 CPU 時(shí)間換取調(diào)度延遲的策略。如果新的 G 很快就能變?yōu)榭蛇\(yùn)行狀態(tài)(例如,另一個(gè) M 正在處理一個(gè)即將完成的短任務(wù),或者一個(gè) I/O 事件即將觸發(fā)),那么自旋可以避免 M 進(jìn)入休眠和隨后被喚醒所帶來(lái)的開銷(這通常涉及操作系統(tǒng)層面的上下文切換,成本相對(duì)較高)。
  • 自旋的條件與限制 :Go runtime 中的自旋不是無(wú)限制的。

a.通常,只有當(dāng)系統(tǒng)中存在其他活躍的 P(意味著其他 M 正在工作,有可能產(chǎn)生新的 G)時(shí),M 才會(huì)進(jìn)入自旋狀態(tài)。如果所有 P 都已空閑,則 M 傾向于直接休眠。

b.同時(shí),runtime 會(huì)限制并發(fā)自旋的 M 的數(shù)量,以避免過(guò)多的 M 同時(shí)無(wú)效自旋。

c.自旋的持續(xù)時(shí)間或迭代次數(shù)是有限的。如果經(jīng)過(guò)短暫的自旋后仍未找到 G,M 將停止自旋,釋放其 P(如果 P 上確實(shí)沒(méi)有 G),并進(jìn)入休眠(park)狀態(tài),將 CPU 讓給其他進(jìn)程或線程。

自旋是一種短期內(nèi)積極尋找任務(wù)的優(yōu)化手段,適用于預(yù)期任務(wù)會(huì)很快出現(xiàn)的場(chǎng)景,以減少調(diào)度開銷,但它確實(shí)會(huì)短暫地增加 CPU 使用率。

在這個(gè)過(guò)程中,P 的狀態(tài)也會(huì)相應(yīng)變化。例如,當(dāng)一個(gè) M 成功與一個(gè) P 綁定并開始查找或執(zhí)行 G 時(shí),P 的狀態(tài)會(huì)是 _Prunning。如果 P 的 LRQ 和 GRQ 都長(zhǎng)時(shí)間為空,并且沒(méi)有 M 依附于它,它可能進(jìn)入 _Pidle 狀態(tài)。

G 的棧數(shù)據(jù)切換發(fā)生在 M 從 g0 棧切換到用戶 G 的棧,以及從用戶 G 的棧切回 g0 棧時(shí)。這個(gè)切換操作會(huì)保存和恢復(fù)各自的棧指針和寄存器等上下文信息。

2. 棧的伸縮與 P 的競(jìng)爭(zhēng)

  • 棧的動(dòng)態(tài)伸縮 :Goroutine 的棧在創(chuàng)建時(shí)通常較小(例如 2KB)。當(dāng) G 執(zhí)行的函數(shù)調(diào)用深度增加,需要的棧空間超過(guò)當(dāng)前大小時(shí),Go runtime 會(huì)觸發(fā)一個(gè)稱為 morestack 的機(jī)制。該機(jī)制會(huì)分配一個(gè)新的、更大的棧段,并將舊棧的內(nèi)容拷貝到新棧段,然后 G 繼續(xù)在新棧上執(zhí)行。這個(gè)過(guò)程對(duì)用戶是透明的。當(dāng)函數(shù)返回,棧使用量減少時(shí),雖然不會(huì)立即縮小,但在垃圾回收期間,如果發(fā)現(xiàn)棧使用率過(guò)低,可能會(huì)進(jìn)行棧的收縮(shrinkstack)。
  • P 的競(jìng)爭(zhēng) :在 Go 程序啟動(dòng)時(shí),會(huì)根據(jù) GOMAXPROCS 創(chuàng)建相應(yīng)數(shù)量的 P。如果 M 的數(shù)量少于 P 的數(shù)量(例如,某些 M 因?yàn)橄到y(tǒng)調(diào)用阻塞了),或者有空閑的 P 和待運(yùn)行的 G,運(yùn)行時(shí)可能會(huì)喚醒或創(chuàng)建新的 M 來(lái)綁定這些 P。一個(gè) M 必須獲取到一個(gè) P 才能運(yùn)行 Go 代碼。如果所有 P 都在 _Prunning 狀態(tài)(即都有 M 在其上運(yùn)行 G),那么新創(chuàng)建的 G 只能進(jìn)入 LRQ 或 GRQ 等待。當(dāng)一個(gè) M 從阻塞的系統(tǒng)調(diào)用返回,或者一個(gè) G 執(zhí)行完畢,它會(huì)嘗試獲取一個(gè) P 來(lái)繼續(xù)執(zhí)行。

3. I/O 操作與網(wǎng)絡(luò)調(diào)度

當(dāng)一個(gè) G (假設(shè)為 Gx,在 M1/P1 上運(yùn)行) 發(fā)起一個(gè)阻塞性的 I/O 操作,比如網(wǎng)絡(luò)讀寫時(shí),情況會(huì)變得特殊:

  • 進(jìn)入系統(tǒng)調(diào)用 :Gx 在 M1 上調(diào)用了一個(gè)阻塞的 read。Go runtime 的 syscall 包中的函數(shù)通常會(huì)進(jìn)行特殊處理。M1 會(huì)即將進(jìn)入阻塞狀態(tài)。
  • 釋放 P :為了不讓 P1 上的其他 G 被餓死,M1 會(huì)釋放 P1。P1 此時(shí) 通常會(huì)連同其 LRQ 中的 G 一起 ,被移交給一個(gè)其他可用的、處于空閑狀態(tài)的 M (例如 M2,可以是已存在的空閑 M,或者是 runtime 根據(jù)需要新創(chuàng)建的 M 來(lái)接管這個(gè) P)。M1 則帶著 Gx 進(jìn)入阻塞的系統(tǒng)調(diào)用。Gx 的狀態(tài)變?yōu)?nbsp;_Gsyscall

這里需要考慮到調(diào)度器內(nèi)部實(shí)現(xiàn)的復(fù)雜性和一些邊緣情況。核心原則是: LRQ 始終與 P 綁定 。當(dāng) M1 因 Gx 的系統(tǒng)調(diào)用而將要阻塞時(shí),它會(huì)釋放 P1。

  • 主要情況 :調(diào)度器會(huì)立即嘗試尋找一個(gè)空閑的 M (M2) 來(lái)接管 P1。如果找到,M2 就綁定 P1,并開始執(zhí)行 P1 的 LRQ 中的 G。這時(shí),P1 及其 LRQ 完整地從 M1 轉(zhuǎn)移到了 M2。
  • 沒(méi)有立即可用的 M:如果暫時(shí)沒(méi)有空閑的 M 可以立即接管 P1,P1 會(huì)被放入一個(gè)空閑 P 隊(duì)列 (pidle 列表)。其 LRQ 中的 G 仍然與 P1 綁定并處于 _Grunnable 狀態(tài)。一旦有 M 可用(例如 M1 從系統(tǒng)調(diào)用返回后變?yōu)榭臻e,或者 sysmon 檢測(cè)到需要更多 M 并創(chuàng)建/喚醒了一個(gè)),這個(gè) M 就會(huì)從 pidle 列表中獲取 P1,并開始執(zhí)行其 LRQ 中的 G。
  • 因此,P1 的 LRQ 中的 G 總是和 P1 在一起 。關(guān)鍵在于 P1 由哪個(gè) M 來(lái)服務(wù)。如果 M1 阻塞了,它就不能服務(wù) P1,所以 P1 必須尋找新的 M,或者等待 M 變?yōu)榭捎谩?/span>
  • 網(wǎng)絡(luò)輪詢器 (Netpoller) :Go runtime 內(nèi)部維護(hù)了一個(gè)網(wǎng)絡(luò)輪詢器(在 Linux 上通常基于 epoll,在 macOS 上基于 kqueue,在 Windows 上基于 iocp)。當(dāng) Gx 發(fā)起網(wǎng)絡(luò) I/O 時(shí),其對(duì)應(yīng)的文件描述符會(huì)被注冊(cè)到這個(gè)網(wǎng)絡(luò)輪詢器中。M1 線程本身會(huì)阻塞在系統(tǒng)調(diào)用上(或者對(duì)于非阻塞 I/O,G 會(huì)等待 netpoller 的通知),但它不再持有 P。
  • I/O 就緒與喚醒 :當(dāng)網(wǎng)絡(luò)輪詢器檢測(cè)到 Gx 等待的文件描述符上的 I/O 操作就緒(例如數(shù)據(jù)可讀),它會(huì)通知調(diào)度器。Gx 會(huì)被標(biāo)記為 _Grunnable,并被放回到某個(gè) P 的 LRQ (可能是原來(lái)的 P1,如果它恰好空閑) 或者 GRQ 中。

Go 的標(biāo)準(zhǔn)庫(kù)網(wǎng)絡(luò)操作在底層通常被封裝為非阻塞模式,并與 netpoller 集成。

  • 注冊(cè)與等待 :當(dāng) Gx 調(diào)用如 net.Conn.Read() 時(shí),如果數(shù)據(jù)尚未到達(dá),runtime 不會(huì)真的讓 M1 線程阻塞在內(nèi)核的 read() 調(diào)用上。相反,它會(huì)將 Gx 的狀態(tài)置為 _Gwaiting,并將與該連接對(duì)應(yīng)的文件描述符 (FD) 注冊(cè)到 netpoller 中,請(qǐng)求 netpoller 在該 FD 可讀時(shí)通知。然后,M1 釋放 P1(或 P1 被其他 M 接管),M1 可以去執(zhí)行其他 G 或者休眠。
  • Netpoller 的監(jiān)控 :Netpoller (通常是一個(gè)獨(dú)立的系統(tǒng)線程或由 sysmon 驅(qū)動(dòng)) 使用操作系統(tǒng)提供的事件通知機(jī)制 (如 epoll_waitkevent 等) 來(lái)同時(shí)監(jiān)控大量已注冊(cè)的 FD。這些機(jī)制允許一個(gè)線程高效地等待多個(gè) FD 上的事件,而無(wú)需為每個(gè) FD 單獨(dú)創(chuàng)建一個(gè)線程。
  • 事件通知 :當(dāng)操作系統(tǒng)內(nèi)核檢測(cè)到某個(gè) FD 上的數(shù)據(jù)已到達(dá)(對(duì)于 read 操作)或可以發(fā)送數(shù)據(jù)(對(duì)于 write 操作)時(shí),它會(huì)通知 netpoller。
  • G 的喚醒 :Netpoller 收到內(nèi)核通知后,會(huì)識(shí)別出是哪個(gè) G 在等待這個(gè) FD 上的事件。它會(huì)將該 G 從 _Gwaiting 狀態(tài)轉(zhuǎn)換回 _Grunnable 狀態(tài),并將其放入一個(gè)運(yùn)行隊(duì)列 (通常是 GRQ,有時(shí)也可能是某個(gè) P 的 LRQ,例如上次運(yùn)行該 G 的 P,以期利用緩存局部性)。
  • 調(diào)度執(zhí)行實(shí)際讀操作 :一旦 Gx 變?yōu)?nbsp;_Grunnable,它就和其他等待調(diào)度的 G 一樣。當(dāng)某個(gè) M/P 組合選中它執(zhí)行時(shí),它會(huì)從之前中斷的地方恢復(fù)。此時(shí),由于 netpoller 已經(jīng)確認(rèn)數(shù)據(jù)就緒,G 可以執(zhí)行實(shí)際的、現(xiàn)在不會(huì)阻塞的 read() 操作來(lái)獲取數(shù)據(jù)。
  • 重新調(diào)度執(zhí)行 :一旦 Gx 變?yōu)?nbsp;_Grunnable,它就和其他可運(yùn)行的 G 一樣,等待某個(gè) M/P 組合來(lái)執(zhí)行它。當(dāng)輪到它時(shí),它會(huì)從上次阻塞的地方繼續(xù)執(zhí)行。

這種機(jī)制確保了少數(shù) G 的阻塞性 I/O 不會(huì)阻塞整個(gè)程序的并發(fā)執(zhí)行。M 的數(shù)量可能會(huì)根據(jù)需要?jiǎng)討B(tài)調(diào)整(在一定范圍內(nèi)),以適應(yīng)負(fù)載情況。

創(chuàng)建一個(gè) go func(){}() 發(fā)生了什么?

當(dāng)你執(zhí)行一行代碼 go func(){ ... }() 時(shí),Go runtime 會(huì)執(zhí)行以下步驟:

  • 創(chuàng)建 G 對(duì)象 :首先,runtime 會(huì)在堆上分配并初始化一個(gè)新的 G 對(duì)象。這個(gè)對(duì)象包含了新 goroutine 的棧信息(初始分配一個(gè)小棧)、程序計(jì)數(shù)器(指向匿名函數(shù)的起始位置)以及其他狀態(tài)信息。

a.設(shè)置初始狀態(tài) :新創(chuàng)建的 G 的初始狀態(tài)被設(shè)置為 _Grunnable,表示它已經(jīng)準(zhǔn)備好運(yùn)行,只等待調(diào)度器的調(diào)度。

b.放入隊(duì)列 :這個(gè)新的 _Grunnable 的 G 通常會(huì)被嘗試放入當(dāng)前 M 所關(guān)聯(lián)的 P 的 LRQ。

  • 如果該 P 的 LRQ 已滿,runtime 會(huì)嘗試將 P 的 LRQ 中的一部分 G(包括這個(gè)新的 G)均衡到 GRQ 中。
  • 在某些情況下,如果創(chuàng)建 G 的 P 處于特殊狀態(tài),或者為了更好的負(fù)載均衡,新的 G 也可能直接被放入 GRQ。
  • 創(chuàng)建 G 的函數(shù)返回 :go 語(yǔ)句本身是一個(gè)非阻塞調(diào)用。執(zhí)行 go 語(yǔ)句的 goroutine 會(huì)繼續(xù)執(zhí)行其后續(xù)代碼,而不會(huì)等待新創(chuàng)建的 goroutine 開始或完成執(zhí)行。
  • 調(diào)度與執(zhí)行 :新創(chuàng)建的 G 現(xiàn)在位于某個(gè)運(yùn)行隊(duì)列中。當(dāng)某個(gè) M(可能就是當(dāng)前的 M,也可能是其他 M)在未來(lái)的某個(gè)調(diào)度點(diǎn)(例如,當(dāng)前 G 執(zhí)行完畢、發(fā)生搶占、或 M 從系統(tǒng)調(diào)用返回時(shí))查找可運(yùn)行的 G 時(shí),它就有機(jī)會(huì)從 LRQ 或 GRQ 中獲取這個(gè)新的 G。獲取到 G 后,M 會(huì)設(shè)置好運(yùn)行環(huán)境(切換到該 G 的棧,設(shè)置 G 的狀態(tài)為 _Grunning 等),然后開始執(zhí)行該匿名函數(shù)內(nèi)的代碼。

整個(gè)過(guò)程與上面描述的 GPM 調(diào)度機(jī)制緊密相連,新的 G 只是作為調(diào)度器可調(diào)度的一個(gè)單元被高效地管理起來(lái)。

調(diào)度策略與搶占機(jī)制

Go 的調(diào)度器采用了一些關(guān)鍵策略來(lái)保證公平性和效率:

  • 工作竊取 (Work Stealing) :如前所述,當(dāng)一個(gè) P 的 LRQ 為空時(shí),其關(guān)聯(lián)的 M 會(huì)嘗試從其他 P 的 LRQ 中“竊取”一半的 G 到自己的 LRQ,或者從 GRQ 中獲取 G。這有助于在 P 之間均勻分配工作負(fù)載,防止某些 P 空閑而另一些 P 過(guò)載。
  • 搶占 (Preemption) :在 Go 的早期版本中(1.14 之前),搶占主要是協(xié)作式的。也就是說(shuō),一個(gè) goroutine 主動(dòng)放棄 CPU 的執(zhí)行權(quán)通常發(fā)生在函數(shù)調(diào)用時(shí)(編譯器會(huì)在函數(shù)入口處插入檢查點(diǎn),判斷是否需要進(jìn)行棧增長(zhǎng)以及是否需要被搶占)、channel 操作、select 語(yǔ)句、以及一些同步原語(yǔ)的調(diào)用點(diǎn)。這意味著如果一個(gè) goroutine 執(zhí)行一個(gè)沒(méi)有任何函數(shù)調(diào)用的密集計(jì)算循環(huán) (for {}),它可能會(huì)長(zhǎng)時(shí)間占據(jù) M,導(dǎo)致同一個(gè) P 上的其他 goroutine 餓死。

從 Go 1.14 版本開始,引入了 基于信號(hào)的異步搶占機(jī)制 (asynchronous preemption) ,以解決上述問(wèn)題:

  • sysmon 后臺(tái)監(jiān)控線程 :Go runtime 有一個(gè)名為 sysmon 的特殊 M(不關(guān)聯(lián) P),它會(huì)定期進(jìn)行一些維護(hù)工作,其中就包括檢查是否有 G 運(yùn)行時(shí)間過(guò)長(zhǎng)(例如,超過(guò)一個(gè)固定的時(shí)間片,通常是 10ms)。
  • 發(fā)送信號(hào) :如果 sysmon 發(fā)現(xiàn)某個(gè) G 在一個(gè) M 上運(yùn)行時(shí)間過(guò)長(zhǎng),它會(huì)向該 M 發(fā)送一個(gè)搶占信號(hào)(例如,在 Unix 系統(tǒng)上是 SIGURG)。
  • 信號(hào)處理 :M 接收到信號(hào)后,會(huì)中斷當(dāng)前正在執(zhí)行的 G。G 的上下文(主要是寄存器)會(huì)被保存,其狀態(tài)會(huì)被標(biāo)記為 _Gpreempted 或類似狀態(tài),然后被放回到運(yùn)行隊(duì)列(通常是 GRQ,以給其他 P 機(jī)會(huì)執(zhí)行它,避免立即在同一個(gè) P 上再次調(diào)度)。
  • 重新調(diào)度 :M 隨后會(huì)進(jìn)入調(diào)度循環(huán)(在其 g0 棧上),選擇下一個(gè)可運(yùn)行的 G 來(lái)執(zhí)行。

這種異步搶占機(jī)制確保了即使是那些沒(méi)有主動(dòng)讓出 CPU 的計(jì)算密集型 goroutine 也能夠被公平地調(diào)度,從而提高了整個(gè)系統(tǒng)的響應(yīng)性和并發(fā)任務(wù)的并行度。它使得調(diào)度器更加健壯,不易受到不良編寫的 goroutine 的影響。

func main 也是一個(gè) goroutine

當(dāng)一個(gè) Go 程序啟動(dòng)時(shí),main 包下的 main 函數(shù)并不是直接在某個(gè)原始線程上執(zhí)行,而是由 Go runtime 創(chuàng)建的第一個(gè)用戶級(jí) goroutine,通常被稱為 main goroutine 。

  • 初始化過(guò)程 :Go 程序的入口點(diǎn)實(shí)際上是 runtime 的一段引導(dǎo)代碼。這段代碼會(huì)負(fù)責(zé)初始化調(diào)度器、垃圾回收器、創(chuàng)建必要的 M 和 P,然后創(chuàng)建一個(gè) G 來(lái)執(zhí)行用戶編寫的 main.main() 函數(shù)。
  • 與其他 goroutine 平等 :這個(gè) main goroutine 在行為上與用戶通過(guò) go 關(guān)鍵字創(chuàng)建的其他 goroutine 是平等的。它也擁有自己的棧,受 GPM 調(diào)度器的管理,可以被搶占,也可以創(chuàng)建新的 goroutine。
  • 程序生命周期 :main goroutine 的結(jié)束標(biāo)志著整個(gè)程序的結(jié)束。當(dāng) main 函數(shù)返回時(shí),Go runtime 會(huì)開始關(guān)閉程序。此時(shí),所有其他仍在運(yùn)行的 goroutine 都會(huì)被強(qiáng)制終止,除非程序使用了像 sync.WaitGroup 這樣的機(jī)制來(lái)顯式等待其他 goroutine 完成。
  • 退出碼 :main 函數(shù)沒(méi)有返回值。程序如果正常退出,通常退出碼為 0。如果發(fā)生 panic 且未被 recover,或者調(diào)用了 os.Exit(code),則會(huì)以相應(yīng)的狀態(tài)退出。

理解 main 函數(shù)本身也是一個(gè) goroutine 有助于更好地認(rèn)識(shí) Go 的并發(fā)模型的一致性:所有用戶代碼都運(yùn)行在 goroutine 之上,由統(tǒng)一的 GPM 模型進(jìn)行調(diào)度和管理。這體現(xiàn)了 Go 在語(yǔ)言層面和運(yùn)行時(shí)層面將并發(fā)作為一等公民的設(shè)計(jì)哲學(xué)。

責(zé)任編輯:姜華 來(lái)源: Piper蛋窩
相關(guān)推薦

2025-06-03 02:00:00

2025-06-09 01:15:00

2025-05-28 03:00:00

2024-01-22 10:18:32

平臺(tái)工程開發(fā)人員技術(shù)

2019-10-08 10:37:46

設(shè)計(jì)技術(shù)程序員

2016-11-29 16:46:17

存儲(chǔ)閃存經(jīng)濟(jì)學(xué)

2015-11-09 10:12:08

大數(shù)據(jù)個(gè)性化推薦

2013-07-11 10:37:20

Java內(nèi)存模型

2009-08-07 14:09:47

垃圾郵件企業(yè)郵件安全梭子魚

2023-03-03 15:37:32

GMP 模型goroutine

2021-02-22 09:30:09

go開發(fā)環(huán)境桌面系統(tǒng)

2018-07-10 08:56:19

編程程序員開發(fā)

2024-12-03 15:15:22

2019-12-26 09:15:44

網(wǎng)絡(luò)IOLinux

2022-04-30 18:42:38

Go編程語(yǔ)言

2021-03-28 20:58:25

Go語(yǔ)言線程

2022-07-06 08:30:36

vuereactvdom

2015-09-02 09:02:21

阿里無(wú)線前端架構(gòu)

2017-05-24 10:12:54

前端FlexboxCSS3

2020-02-27 21:03:30

調(diào)度器架構(gòu)效率
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 天堂一区二区三区 | 欧美在线观看一区 | av一二三区 | 韩日一区二区三区 | 亚洲一区二区三区免费视频 | 久久精品成人 | 免费看a| a成人| 日韩欧美三级 | 一区二区三区小视频 | 美女爽到呻吟久久久久 | 日韩成人在线播放 | 欧美性乱 | 久久99精品久久久 | 欧美一级特黄aaa大片在线观看 | 久久久久国产精品一区二区 | 欧洲在线视频 | 91在线精品播放 | 久久国内精品 | 99久久免费精品国产免费高清 | 欧美videosex性极品hd | 久久激情网| 国产成人免费观看 | 午夜天堂精品久久久久 | 日本视频一区二区三区 | 神马久久久久久久久久 | 天天射夜夜操 | 欧美激情黄色 | 日韩在线一区二区三区 | 毛片a区| 黄色大片免费网站 | 欧美精品乱码久久久久久按摩 | 黑色丝袜三级在线播放 | 欧美久久一区二区三区 | 天天操天天怕 | 国产成人精品一区二区三区四区 | 久久综合狠狠综合久久 | 精品亚洲永久免费精品 | 欧美久久久电影 | 波多野结衣精品 | 日韩中文字幕免费在线观看 |