1. 程式人生 > >弄懂goroutine排程原理

弄懂goroutine排程原理

goroutine簡介

golang語言作者Rob Pike說,“Goroutine是一個與其他goroutines 併發執行在同一地址空間的Go函式或方法。一個執行的程式由一個或更多個goroutine組成。它與執行緒、協程、程序等不同。它是一個goroutine“。

  • goroutine通過通道來通訊,而協程通過讓出和恢復操作來通訊;
  • goroutine 通過Golang 的排程器進行排程,而協程通過程式本身排程;

簡單的說就是Golang自己實現了協程並叫做goruntine(本文稱Go協程),且比協程更強大。

goroutine排程原理

上面說到Go協程是通過Golang的排程器進行排程的,其中排程器的執行緒模型為兩級執行緒模型。

有關兩級執行緒模型的介紹,可以看這篇文章

我們來看下Golang實現的兩級執行緒模型是怎樣的。首先要知道這三個字母代表的含義

  • M:代表核心級的執行緒
  • P:全程Processor,代表執行Go協程所需要的資源(上下文環境)
  • G:代表Go協程

    我們先看下為實現排程Golang定義了這些資料結構存M,P,G
名稱 作用範圍 描述
全域性M列表 Go的執行時 存放所有M的單向連結串列
全域性P列表 Go的執行時 存放所有P的陣列
全域性G列表 Go的執行時 存放所有G的切片
排程器的空閒M列表 排程器 存放空閒M的單向連結串列
排程器的空閒P列表 排程器 存放空閒P的單向連結串列
排程器的自由G列表 排程器 存放自由G的單向連結串列(有兩個)
排程器的可執行G佇列 排程器 存放可執行G的佇列
P的自由G列表 本地P 存放當前P中自由G的單向連結串列
P的可執行G佇列 本地P 存放當前P中可執行G的佇列

然後從上往下解析Go的兩級執行緒模型圖

(1)M和核心執行緒之間是一對一的關係,一個M在其生命週期中,只會和一個核心執行緒關聯,所以不會出現對核心執行緒的頻繁切換;

Golang的執行時執行系統監控和垃圾回收等任務時候會導致建立M,M空閒時不會被銷燬,而是放到一個排程器的空閒M列表中,等待與P關聯,M預設數量為10000

(2)P和M之間是多對多的關係,P和G之間是一對多的關係,他們的關聯是易變的,由Golang的排程器完成排程;

Golang的執行時按規則排程,讓P和不同的M建立或斷開關聯,使得P中的G能夠及時獲得執行時機

(3)P的數量預設為CPU總核心數,最大為256,當P沒有可執行的G時候(P的可執行G佇列為空),P會被放到排程器的空閒P列表中,等待M與它關聯;

P有可能會被銷燬,如執行時用runtime.GOMAXPROCS把P的數量從32降到16時,剩餘16個會被銷燬,它們原來的G會先轉到排程器可執行的G佇列自由G列表

(4)每個P中有可執行的G佇列(如圖中最下面的那行G)和自由G列表(圖中未畫出來),當G的程式碼執行完後,該G不會被銷燬,而是被放到P的自由G列表排程器的自由G列表。如果程式新建了Go協程,排程器會在自由G列表中取一個G,然後把Go協程的函式賦值到G中(如果自由G列表為空,就建立一個G);

可見Golang排程器在排程時很大程度複用了M,P,G

(5)在Go程式初始化後,排程器首先進行一輪排程,此時用M去搜索可執行的G。其中我們的main函式也是一個G,找到可執行的G後就執行它;

至於怎麼找可執行的G呢?答案是到處找,想盡辦法找(這裡只列出一部分地方)。

  • 本地P的可執行的G佇列
  • 排程器的可執行的G佇列
  • 其他P的可執行的G佇列

(6)P的可執行G佇列最大隻能存放長度為256的G,當佇列滿後,排程器會把一半的G轉到排程器的可執行G佇列

系統監控

上面大概描述了關於goroutine排程的流程。現在還存在一個問題,那就是當Go協程很多(併發量大)時候,顯然G是不能一直執行下去的,因為也需要把執行機會留給其他的G。此時Golang執行時的系統監控就起作用了。
一般情況,當G執行時間超過10ms後,該G就會被系統告知需要停止了,讓其他G執行。(這裡情況比較複雜,並不能確保每個G都能被公平執行)

以下特殊情況該G不需要停止

  • P的可執行G佇列為空(沒有其他G可執行)
  • 有空閒的M在尋找可執行的G(沒有其他G可執行)
  • 空閒的P(還有P閒著)

總結

Golang以兩級執行緒實現模型,自己實現goruntine和排程器,優勢在於並行和非常低的資源使用。

主要體現:

  • 記憶體消耗方面(每個Go協程佔的記憶體遠小於執行緒佔的記憶體)
  • 切換(排程)開銷方面
  • 執行緒切換涉及模式切換(從使用者態切換到核心態)

此外,Go協程執行任務完成的順序並不都是按我們預期的那樣(程式不加以控制的情況下),特別在一些耗時較長的任務中。且每個Go協程執行的時間也不是絕對公平的。

如有錯誤地方,還請狂噴