[Go] goroutine是怎麼工作的?

在這篇Go中最強的魔法: 併發程式執行的文章中,我們探討了併發的基本概念以及Go語言中實現併發的機制—Goroutine。這些內容對於剛接觸Go的Gopher來說,可以說是入門級的學習資料。

但是,你可能會好奇,既然Go不是採用傳統的基於執行緒的併發模型,那Go的運行時是如何管理和調度(Schedule)眾多Goroutine的呢?實際上,這些是Go語言核心開發團隊的關注點,普通開發者通常不需要深入了解。不過深入理解Goroutine的調度原理對於寫出高效能的Go程式碼是非常有幫助的。

接下來,讓我們一起簡要探索一下Goroutine調度器的工作原理。

Goroutine調度器

談到「調度」,我們首先會想到作業系統如何管理進程和執行緒。作業系統的調度器會把多個執行緒分配到物理CPU上運行。與此相對應,Goroutine的資源消耗非常小。我們之前提到過,每個Goroutine只需2KB的stack空間。並且,Goroutine的調度切換並不需要陷入作業系統核心,因此成本非常低。這使得一個Go程式可以創建成千上萬的Goroutine來實現併發。

這些Goroutine是如何被調度到CPU上運行的呢?實際上,它們是由一個稱為Goroutine調度器(Goroutine Scheduler)的機制來管理的。但要注意,這裡提到的「CPU」是在引號中,因為從Go程式的視角來看,它與作業系統的交互是有限的。

實際上,對於作業系統而言,一個Go程式只是一個普通的用戶空間(user space)應用程式。作業系統並不直接識別Goroutine,因此所有的Goroutine調度都由Go自己來完成。這就導致了Go runtime必須在其自身內部管理Goroutine之間對「CPU」資源的競爭。

那麼,在Go程式中,Goroutine實際上是在競爭什麼資源呢?答案是作業系統執行緒。因此,Goroutine調度器的工作就是把Goroutine根據一定的算法分配到不同的作業系統執行緒中去執行。

Goroutine調度器模型 - G-P-M模型

在Go語言的領域中,Goroutine的調度機制、垃圾回收(GC)以及記憶體管理屬於一些較為深奧且複雜的概念。這些主題每一個都可以獨立成章,且隨著Go語言版本的更新,這些主題的實現細節也在不斷進化。本文將嘗試為你揭開基於G-P-M模型的Goroutine調度原理的神秘面紗。如果你對這方面的知識渴望更深入的了解,可以從這裡開始深挖Go的原始碼,探索更多細節。

首先,讓我們來看看G、P、M這三者的定義,它們在Go語言的原始碼中有詳細的闡述,位置在$GOROOT/src/runtime/runtime2.go。在這個檔案中,你會發現G、P、M這三種結構的定義都非常龐大,每個結構包含了許多程式碼和定義。調度器這塊核心的程式碼相當複雜,考慮到的情況繁多,程式碼之間的「耦合」也十分緊密。不過,通過這些複雜的程式碼,我們還是可以窺見G、P、M各自的主要功能,以下簡要說明:

  • **G (Goroutine)**:負責儲存Goroutine的運行堆棧(Queue/Heap)、狀態以及任務函數等資訊,G是可被重用的。
  • **P (邏輯處理器)**:其數量決定了系統能夠同時並行運行的Goroutine的數量,P的主要作用是管理Goroutine Queue、Linked List及一些Cache和狀態。
  • **M (執行計算資源)**:在綁定了有效的P之後,會進入調度循環。這個循環主要從P的Local Queue或Global Queue中取得Goroutine,切換到相應的運行stack/heap上執行,並在執行完畢後通過goexit進行清理,然後返回M,這一過程會不斷重複。值得注意的是,M本身不保留任何Goroutine的狀態,這也是Goroutine能夠跨M調度的基礎。

被搶佔的Goroutine調度

接下來,我們來討論當Goroutine在沒有進行系統調用、I/O操作或在channel上阻塞時,調度器是如何進行工作的。在這些情況下,Go語言的運行時會利用搶佔式調度來暫停當前Goroutine,並調度其他可運行的Goroutine。一般來說,除了極端情況下的無限迴圈,只要Goroutine執行了函數調用,Go運行時就有機會對其進行搶佔。

Go語言在啟動時會運行一個名為sysmon的M(通常被稱為監控線程),這個特殊的M在Go程式運行的整個過程中發揮著關鍵作用,並且它無需綁定P即可運行(以g0的形式出現)。除此之外,當Goroutine在channel操作或網絡I/O上阻塞,或者在系統調用中阻塞時,Go運行時會有特定的調度策略來處理這些情況,確保整個系統的有效運行。

總結

  • Goroutine調度器是Go語言中實現併發控制的核心,負責將Goroutine有效地分配到CPU資源上。
  • G-P-M模型是理解Go中Goroutine調度的基礎,涉及Goroutine、邏輯處理器和執行資源的交互。
  • 透過深入理解Goroutine的調度機制,開發者可以更好地掌握Go語言的併發模型,寫出更高效、更可靠的程式碼。