讓你很快就能理解-go的協程調度原理

什麼是協程

對於進程、線程,都是有內核進行調度,有CPU時間片的概念,進行搶佔式調度。協程,又稱微線程,纖程。英文名Coroutine。協程的調用有點類似子程序,如程序A調用了子程序B,子程序B調用了子程序C,當子程序C結束了返回子程序B繼續執行之後的邏輯,當子程序B運行結束了返回程序A,直到程序A運行結束。但是和子程序相比,協程有掛起的概念,協程可以掛起跳轉執行其他協程,合適的時機再跳轉回來。

線程調度原理

N:1模型,多個用戶空間線程在1個內核空間線程上運行。優勢是上下文切換非常快,因為這些線程都在內核態運行,但是無法利用多核系統的優點。

1:1模型,1個內核空間線程運行一個用戶空間線程。這種充分利用了多核系統的優勢但是上下文切換非常慢,因為每一次調度都會在用戶態和內核態之間切換。POSIX線程模型(pthread)就是這麼做的。

M:N模型,內核空間開啟多個內核線程,一個內核空間線程對應多個用戶空間線程。效率非常高,但是管理複雜。

goroutine調度原理

本質上goroutine就是協程,但是完全運行在用戶態,借鑑了M:N模型。如下圖

讓你很快就能理解-go的協程調度原理

相比其他語言,golang採用了MPG模型管理協程,更加高效,但是管理非常複雜。

M:內核級線程

G:代表一個goroutine

P:Processor,處理器,用來管理和執行goroutine的。

G-M-P三者的關係與特點:

P的個數取決於設置的GOMAXPROCS,go新版本默認使用最大內核數,比如你有8核處理器,那麼P的數量就是8

M的數量和P不一定匹配,可以設置很多M,M和P綁定後才可運行,多餘的M處於休眠狀態。

P包含一個LRQ(Local Run Queue)本地運行隊列,這裡面保存著P需要執行的協程G的隊列

除了每個P自身保存的G的隊列外,調度器還擁有一個全局的G隊列GRQ(Global Run Queue),這個隊列存儲的是所有未分配的協程G。

假設我們的主機是單核的,那麼協程運行圖是這樣:

讓你很快就能理解-go的協程調度原理

紅色部分表示掛起和休眠,黃色部分表示準備就緒等待運行,綠色部分表示正在運行。

主機是單核的所以只有一個處理器P,但是系統初始化了兩個線程M0和M1,處理器P優先綁定了M0線程,M1進入休眠狀態。

P的LRQ隊列裡有G1,G2,G3等待處理。P目前正在處理G0,全局等待隊列GRQ裡保存著G4,G5,表示這兩個協程還未分配給P。

如果G0在短時間內處理完,P就會從LRQ中取出G1繼續處理。並且將GRQ全局隊列中的部分協程加入LRQ中。

假設現在G1處理速度很慢,系統就會讓M0線程休眠,掛起協程G1,喚醒線程M1進行處理其他的協程。這裡M1會將M0未處理的協程取走處理。

等到M1協程隊列中所有協程處理完再次喚醒M0,或者M1處理某個協程時間較長被掛起,M0也會被喚醒。

上面的討論是單核主機情況,如果是多核的,就會運行多個P和M。

M0和M1分別運行在不同的內核中,M0處理G1,G2,G3,M1處理G4,G5,G6。

有人會問,當M0處理完所有的協程,而M1還未處理完,系統會如何做呢?

M0會取走M1的一半數量未處理的協程。

總結

golang協程設計非常優秀,一方面極大的利用了內核線程和處理器資源,另一方面每個處理器的LRQ隊列的協程都處於用戶態,這些協程的處理和掛起操作都是用戶態的,協程切換開銷非常小。相比其他語言的線程設計,更加輕量和高效。

以上就是golang協程調度原理。感謝關注


分享到:


相關文章: