Go語言 調(diào)度器相關(guān)數(shù)據(jù)結(jié)構(gòu)

2018-07-25 17:24 更新

Go的調(diào)度的實(shí)現(xiàn),涉及到幾個(gè)重要的數(shù)據(jù)結(jié)構(gòu)。運(yùn)行時(shí)庫用這幾個(gè)數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)goroutine的調(diào)度,管理goroutine和物理線程的運(yùn)行。這些數(shù)據(jù)結(jié)構(gòu)分別是結(jié)構(gòu)體G,結(jié)構(gòu)體M,結(jié)構(gòu)體P,以及Sched結(jié)構(gòu)體。前三個(gè)的定義在文件runtime/runtime.h中,而Sched的定義在runtime/proc.c中。Go語言的調(diào)度相關(guān)實(shí)現(xiàn)也是在文件proc.c中。

結(jié)構(gòu)體G

G是goroutine的縮寫,相當(dāng)于操作系統(tǒng)中的進(jìn)程控制塊,在這里就是goroutine的控制結(jié)構(gòu),是對(duì)goroutine的抽象。其中包括goid是這個(gè)goroutine的ID,status是這個(gè)goroutine的狀態(tài),如Gidle,Grunnable,Grunning,Gsyscall,Gwaiting,Gdead等。

struct G
{
    uintptr    stackguard;    // 分段棧的可用空間下界
    uintptr    stackbase;    // 分段棧的棧基址
    Gobuf    sched;        //進(jìn)程切換時(shí),利用sched域來保存上下文
    uintptr    stack0;
    FuncVal*    fnstart;        // goroutine運(yùn)行的函數(shù)
    void*    param;        // 用于傳遞參數(shù),睡眠時(shí)其它goroutine設(shè)置param,喚醒時(shí)此goroutine可以獲取
    int16    status;        // 狀態(tài)Gidle,Grunnable,Grunning,Gsyscall,Gwaiting,Gdead
    int64    goid;        // goroutine的id號(hào)
    G*    schedlink;
    M*    m;        // for debuggers, but offset not hard-coded
    M*    lockedm;    // G被鎖定只能在這個(gè)m上運(yùn)行
    uintptr    gopc;    // 創(chuàng)建這個(gè)goroutine的go表達(dá)式的pc
    ...
};

結(jié)構(gòu)體G中的部分域如上所示??梢钥吹剑渲邪藯P畔tackbase和stackguard,有運(yùn)行的函數(shù)信息fnstart。這些就足夠成為一個(gè)可執(zhí)行的單元了,只要得到CPU就可以運(yùn)行。

goroutine切換時(shí),上下文信息保存在結(jié)構(gòu)體的sched域中。goroutine是輕量級(jí)的線程或者稱為協(xié)程,切換時(shí)并不必陷入到操作系統(tǒng)內(nèi)核中,所以保存過程很輕量??匆幌陆Y(jié)構(gòu)體G中的Gobuf,其實(shí)只保存了當(dāng)前棧指針,程序計(jì)數(shù)器,以及goroutine自身。

struct Gobuf
{
    // The offsets of these fields are known to (hard-coded in) libmach.
    uintptr    sp;
    byte*    pc;
    G*    g;
    ...
};

記錄g是為了恢復(fù)當(dāng)前goroutine的結(jié)構(gòu)體G指針,運(yùn)行時(shí)庫中使用了一個(gè)常駐的寄存器extern register G* g,這個(gè)是當(dāng)前goroutine的結(jié)構(gòu)體G的指針。這樣做是為了快速地訪問goroutine中的信息,比如,Go的棧的實(shí)現(xiàn)并沒有使用%ebp寄存器,不過這可以通過g->stackbase快速得到。"extern register"是由6c,8c等實(shí)現(xiàn)的一個(gè)特殊的存儲(chǔ)。在ARM上它是實(shí)際的寄存器;其它平臺(tái)是由段寄存器進(jìn)行索引的線程本地存儲(chǔ)的一個(gè)槽位。在linux系統(tǒng)中,對(duì)g和m使用的分別是0(GS)和4(GS)。需要注意的是,鏈接器還會(huì)根據(jù)特定操作系統(tǒng)改變編譯器的輸出,例如,6l/linux下會(huì)將0(GS)重寫為-16(FS)。每個(gè)鏈接到Go程序的C文件都必須包含runtime.h頭文件,這樣C編譯器知道避免使用專用的寄存器。

結(jié)構(gòu)體M

M是machine的縮寫,是對(duì)機(jī)器的抽象,每個(gè)m都是對(duì)應(yīng)到一條操作系統(tǒng)的物理線程。M必須關(guān)聯(lián)了P才可以執(zhí)行Go代碼,但是當(dāng)它處理阻塞或者系統(tǒng)調(diào)用中時(shí),可以不需要關(guān)聯(lián)P。

struct M
{
    G*    g0;        // 帶有調(diào)度棧的goroutine
    G*    gsignal;    // signal-handling G 處理信號(hào)的goroutine
    void    (*mstartfn)(void);
    G*    curg;        // M中當(dāng)前運(yùn)行的goroutine
    P*    p;        // 關(guān)聯(lián)P以執(zhí)行Go代碼 (如果沒有執(zhí)行Go代碼則P為nil)
    P*    nextp;
    int32    id;
    int32    mallocing; //狀態(tài)
    int32    throwing;
    int32    gcing;
    int32    locks;
    int32    helpgc;        //不為0表示此m在做幫忙gc。helpgc等于n只是一個(gè)編號(hào)
    bool    blockingsyscall;
    bool    spinning;
    Note    park;
    M*    alllink;    // 這個(gè)域用于鏈接allm
    M*    schedlink;
    MCache    *mcache;
    G*    lockedg;
    M*    nextwaitm;    // next M waiting for lock
    GCStats    gcstats;
    ...
};

這里也是截取結(jié)構(gòu)體M中的部分域。和G類似,M中也有alllink域?qū)⑺械腗放在allm鏈表中。lockedg是某些情況下,G鎖定在這個(gè)M中運(yùn)行而不會(huì)切換到其它M中去。M中還有一個(gè)MCache,是當(dāng)前M的內(nèi)存的緩存。M也和G一樣有一個(gè)常駐寄存器變量,代表當(dāng)前的M。同時(shí)存在多個(gè)M,表示同時(shí)存在多個(gè)物理線程。

結(jié)構(gòu)體M中有兩個(gè)G是需要關(guān)注一下的,一個(gè)是curg,代表結(jié)構(gòu)體M當(dāng)前綁定的結(jié)構(gòu)體G。另一個(gè)是g0,是帶有調(diào)度棧的goroutine,這是一個(gè)比較特殊的goroutine。普通的goroutine的棧是在堆上分配的可增長(zhǎng)的棧,而g0的棧是M對(duì)應(yīng)的線程的棧。所有調(diào)度相關(guān)的代碼,會(huì)先切換到該goroutine的棧中再執(zhí)行。

結(jié)構(gòu)體P

Go1.1中新加入的一個(gè)數(shù)據(jù)結(jié)構(gòu),它是Processor的縮寫。結(jié)構(gòu)體P的加入是為了提高Go程序的并發(fā)度,實(shí)現(xiàn)更好的調(diào)度。M代表OS線程。P代表Go代碼執(zhí)行時(shí)需要的資源。當(dāng)M執(zhí)行Go代碼時(shí),它需要關(guān)聯(lián)一個(gè)P,當(dāng)M為idle或者在系統(tǒng)調(diào)用中時(shí),它也需要P。有剛好GOMAXPROCS個(gè)P。所有的P被組織為一個(gè)數(shù)組,在P上實(shí)現(xiàn)了工作流竊取的調(diào)度器。

struct P
{
    Lock;
    uint32    status;  // Pidle或Prunning等
    P*    link;
    uint32    schedtick;   // 每次調(diào)度時(shí)將它加一
    M*    m;    // 鏈接到它關(guān)聯(lián)的M (nil if idle)
    MCache*    mcache;

    G*    runq[256];
    int32    runqhead;
    int32    runqtail;

    // Available G's (status == Gdead)
    G*    gfree;
    int32    gfreecnt;
    byte    pad[64];
};

結(jié)構(gòu)體P中也有相應(yīng)的狀態(tài):

Pidle,
Prunning,
Psyscall,
Pgcstop,
Pdead,

注意,跟G不同的是,P不存在waiting狀態(tài)。MCache被移到了P中,但是在結(jié)構(gòu)體M中也還保留著。在P中有一個(gè)Grunnable的goroutine隊(duì)列,這是一個(gè)P的局部隊(duì)列。當(dāng)P執(zhí)行Go代碼時(shí),它會(huì)優(yōu)先從自己的這個(gè)局部隊(duì)列中取,這時(shí)可以不用加鎖,提高了并發(fā)度。如果發(fā)現(xiàn)這個(gè)隊(duì)列空了,則去其它P的隊(duì)列中拿一半過來,這樣實(shí)現(xiàn)工作流竊取的調(diào)度。這種情況下是需要給調(diào)用器加鎖的。

Sched

Sched是調(diào)度實(shí)現(xiàn)中使用的數(shù)據(jù)結(jié)構(gòu),該結(jié)構(gòu)體的定義在文件proc.c中。

struct Sched {
    Lock;

    uint64    goidgen;

    M*    midle;     // idle m's waiting for work
    int32    nmidle;     // number of idle m's waiting for work
    int32    nmidlelocked; // number of locked m's waiting for work
    int3    mcount;     // number of m's that have been created
    int32    maxmcount;    // maximum number of m's allowed (or die)

    P*    pidle;  // idle P's
    uint32    npidle;  //idle P的數(shù)量
    uint32    nmspinning;

    // Global runnable queue.
    G*    runqhead;
    G*    runqtail;
    int32    runqsize;

    // Global cache of dead G's.
    Lock    gflock;
    G*    gfree;

    int32    stopwait;
    Note    stopnote;
    uint32    sysmonwait;
    Note    sysmonnote;
    uint64    lastpoll;

    int32    profilehz;    // cpu profiling rate
}

大多數(shù)需要的信息都已放在了結(jié)構(gòu)體M、G和P中,Sched結(jié)構(gòu)體只是一個(gè)殼??梢钥吹?,其中有M的idle隊(duì)列,P的idle隊(duì)列,以及一個(gè)全局的就緒的G隊(duì)列。Sched結(jié)構(gòu)體中的Lock是非常必須的,如果M或P等做一些非局部的操作,它們一般需要先鎖住調(diào)度器。

links


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)