首頁 > 軟體

一文詳解Golang協程排程器scheduler

2022-07-31 14:01:04

1. 排程器scheduler的作用

我們都知道,在Go語言中,程式執行的最小單元是gorouines。

然而程式的執行最終都是要交給作業系統來執行的,以Java為例,Java中的一個執行緒對應的就是作業系統中的執行緒,以此來實現在作業系統中的執行。在Go中,gorouines比執行緒更輕量級,其與作業系統的執行緒也不是一一對應的關係,然而,最終我們想要執行程式,還是要藉助作業系統的執行緒來完成,排程器scheduler的工作就是完成gorouines到作業系統執行緒的排程。

2. GMP模型

當我們執行go fun(){}時,會生成一個g,優先放置在建立他的p的本地佇列中,如果本地佇列已滿,那麼會放置在全域性佇列中。

g的執行需要藉助p與m,p是執行器,只有獲得p的g才能執行,p的執行需要掛在m上,m對應的是作業系統中的執行緒,p的數量與CPU的核數相同。

goroutine執行所需要的上下文資訊都是存放在g的資料結構當中的,所以g可以依靠任意的p或者m執行,而對於作業系統而言,其並不能看到p與g的排程過程,這些過程對於作業系統執行緒來說都是連續的,所以省去了執行緒上下文切換的開銷。

g的資料結構如下所示:

type g struct {
    stack       stack   // g自己的棧

    m            *m      // 執行當前g的m
    sched        gobuf   // 儲存了g的現場,goroutine切換時通過它來恢復
    atomicstatus uint32  // g的狀態Gidle,Grunnable,Grunning,Gsyscall,Gwaiting,Gdead
    goid         int64
    schedlink    guintptr // 下一個g, g連結串列

    preempt       bool //搶佔標記

    lockedm        muintptr // 鎖定的M,g中斷恢復指定M執行
    gopc           uintptr  // 建立該goroutine的指令地址
    startpc        uintptr  // goroutine 函數的指令地址
}

p的資料結構如下所示:

type p struct {
    id          int32
    status      uint32 // 狀態
    link        puintptr // 下一個P, P連結串列
    m           muintptr // 擁有這個P的M
    mcache      *mcache  

    // P本地runnable狀態的G佇列
    runqhead uint32
    runqtail uint32
    runq     [256]guintptr
    
    runnext guintptr // 一個比runq優先順序更高的runnable G

    // 狀態為dead的G連結串列,在獲取G時會從這裡面獲取
    gFree struct {
        gList
        n int32
    }

    gcBgMarkWorker       guintptr // (atomic)
    gcw gcWork

}

m的資料結構如下所示:

type m struct {
    g0      *g     // g0, 每個M都有自己獨有的g0

    curg          *g       // 當前正在執行的g
    p             puintptr // 當前用於的p
    nextp         puintptr // 當m被喚醒時,首先擁有這個p
    id            int64
    spinning      bool // 是否處於自旋

    park          note
    alllink       *m // on allm
    schedlink     muintptr // 下一個m, m連結串列
    mcache        *mcache  // 記憶體分配
    lockedg       guintptr // 和 G 的lockedm對應
    freelink      *m // on sched.freem

} 

通過gmp模型,我們能解決gorouines到作業系統執行緒的對映問題,gorouines之間的切換是在使用者態完成的,在作業系統的視角來看,執行緒的上下文切換並不頻繁,因此就少了很多陷入核心的過程,所以有更好的並行效果。

3. 排程機制

1)work stealing機制

當一個p上的g執行完之後,他會嘗試從其他的p佇列中竊取g來執行,以減少作業系統執行緒的切換動作。

2)hand off機制

這個是針對m來說的,有的時候m可能因為g的訊號呼叫而被作業系統阻塞,這個時候p就會掛載去另一個m繼續執行可以執行的g,當阻塞的m就緒之後,會給p發訊號,召喚他回來繼續進行後續操作。

到此這篇關於一文詳解Golang協程排程器scheduler的文章就介紹到這了,更多相關Golang scheduler內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com