Golang協程調度二:協程切換原理

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

概述

協程是Golang中的輕量級線程,麻雀雖小五髒俱全,Golang管理協程時也必然會涉及到協程之間的切換:阻塞的協程被切換出去,可啟動並執行協程被切換進來。我們在本章節就來仔細分析下協程如何切換。

TLS

thread local storage:

getg()

goget()用來擷取當前線程正在執行的協程g。該協程g被儲存在TLS中。

mcall()

mcall在golang需要進行協程切換時被調用,用來儲存被切換出去協程的資訊,並在當前線程的g0協程堆棧上執行新的函數。一般情況下,會在新函數中執行一次schedule()來挑選新的協程來運行。接下來我們就看看mcall的實現。

調用時機

系統調用返回

當執行系統調用的線程從系統調用中返回後,有可能需要執行一次新的schedule,此時可能會調用mcall來完成該工作,如下:

func exitsyscall(dummy int32) {    ......    // Call the scheduler.     mcall(exitsyscall0)    ......}

在exitsyscall0中如果可能會放棄當前協程並執行一次schedule,挑選新的協程來佔有m。

由於阻塞放棄執行

由於某些原因,當前執行的協程可能會被阻塞,如管道讀寫時條件無法滿足,則當前協程會被阻塞直到條件滿足。

在gopark()函數中,便會調用該mcall放棄當前協程並執行一次協程調度。

func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason string, traceEv byte, traceskip int) {    mp := acquirem()    gp := mp.curg     status := readgstatus(gp)    if status != _Grunning && status != _Gscanrunning {        throw("gopark: bad g status")    }    mp.waitlock = lock    mp.waitunlockf = *(*unsafe.Pointer)(unsafe.Pointer(&unlockf))    gp.waitreason = reason    mp.waittraceev = traceEv    mp.waittraceskip = traceskip    releasem(mp)    // can't do anything that might move the G between Ms here.     mcall(park_m)}

而park_m函數我們在後面會分析,它放棄之前執行的協程並調用一次schedule()挑選新的協程來執行。

執行原理

前面我們主要描述了mcall被調用的時機,現在我們要來看看mcall的實現原理。

mcall的函數原型是:

func mcall(fn func(*g))

這裡fn的參數指的是在調用mcall之前正在啟動並執行協程。

我們前面說到,mcall的主要作用是協程切換,它將當前正在執行的協程狀態儲存起來,然後在m->g0的堆棧上調用新的函數。 在新的函數內會將之前啟動並執行協程放棄,然後調用一次schedule()來挑選新的協程運行。

// func mcall(fn func(*g)) // Switch to m->g0's stack, call fn(g). // Fn must never return.  It should gogo(&g->sched) // to keep running g. TEXT runtime·mcall(SB), NOSPLIT, $0-8    // DI中儲存參數fn     MOVQ    fn+0(FP), DI     get_tls(CX)    // 擷取當前正在啟動並執行協程g資訊     // 將其狀態儲存在g.sched變數     MOVQ    g(CX), AX   // save state in g->sched     MOVQ    0(SP), BX   // caller's PC     MOVQ    BX, (g_sched+gobuf_pc)(AX)    LEAQ    fn+0(FP), BX    // caller's SP     MOVQ    BX, (g_sched+gobuf_sp)(AX)    MOVQ    AX, (g_sched+gobuf_g)(AX)    MOVQ    BP, (g_sched+gobuf_bp)(AX)    // switch to m->g0 & its stack, call fn     MOVQ    g(CX), BX    MOVQ    g_m(BX), BX    MOVQ    m_g0(BX), SI    CMPQ    SI, AX  // if g == m->g0 call badmcall     JNE 3(PC)    MOVQ    $runtime·badmcall(SB), AX    JMP AX    MOVQ    SI, g(CX)   // g = m->g0     // 切換到m->g0堆棧     MOVQ    (g_sched+gobuf_sp)(SI), SP  // sp = m->g0->sched.sp     // 參數AX為之前啟動並執行協程g     PUSHQ   AX    MOVQ    DI, DX    MOVQ    0(DI), DI     // 在m->g0堆棧上執行函數fn     CALL    DI     POPQ    AX    MOVQ    $runtime·badmcall2(SB), AX    JMP AXRET 

如何擷取當前協程執行資訊

前兩句理解起來可能比較晦澀:

buf+0(FP) 其實就是擷取gosave的第一個參數(gobuf地址),參考 A Quick Guide to Go’s Assembler

The FP pseudo-register is a virtual frame pointer used to refer to function arguments. The compilers maintain a virtual frame pointer and refer to the arguments on the stack as offsets from that pseudo-register. Thus 0(FP) is the first argument to the function, 8(FP) is the second (on a 64-bit machine), and so on. However, when referring to a function argument this way, it is necessary to place a name at the beginning, as in first_arg+0(FP) and second_arg+8(FP).

LEAQ buf+0(FP), BX則是擷取到第一個參數的儲存地址,而根據golang的堆棧布局,這個地址其實是調用者的sp,如下:

接下來的幾句比較容易理解,在第一句擷取了gobuf的地址後,接下來將一些相關成員設定成合適的value。 最關鍵的是以下幾句

get_tls(CX)MOVQ    g(CX), BX MOVQ BX, gobuf_g(AX)

這幾句的作用是從TLS中擷取當前線程啟動並執行g,然後將其儲存在gobuf的成員g。

gosave()

gosave在golang協程切換時被調用,用來儲存被切換出去協程的資訊,以便在下次該協程被重新調度執行時可以快速恢複出協程的執行內容。

與協程調度相關的資料結構如下:

type g struct {    stack       stack      stackguard0 uintptr     stackguard1 uintptr     ......    sched       gobuf    ......}// gobuf記錄與協程切換相關資訊 type gobuf struct {    sp   uintptr     pc   uintptr     g    guintptr    ctxt unsafe.Pointer     ret  uintreg    lr   uintptr     bp   uintptr }

gosave是用組合語言寫的,效能比較高,但理解起來就沒那麼容易。

TODO: gosave()的調用路徑是什麼呢?

// void gosave(Gobuf*)// save state in Gobuf; setjmp TEXT runtime·gosave(SB), NOSPLIT, $0-8     MOVQ    buf+0(FP), AX           // gobuf    LEAQ    buf+0(FP), BX           // caller's SP    MOVQ    BX, gobuf_sp(AX)    MOVQ    0(SP), BX               // caller's PC    MOVQ BX, gobuf_pc(AX)    MOVQ $0, gobuf_ret(AX)    MOVQ $0, gobuf_ctxt(AX)    MOVQ BP, gobuf_bp(AX)    get_tls(CX)    MOVQ    g(CX), BX     MOVQ BX, gobuf_g(AX)RET 

前兩句理解起來可能比較晦澀:

buf+0(FP) 其實就是擷取gosave的第一個參數(gobuf地址),參考 A Quick Guide to Go’s Assembler

The FP pseudo-register is a virtual frame pointer used to refer to function arguments. The compilers maintain a virtual frame pointer and refer to the arguments on the stack as offsets from that pseudo-register. Thus 0(FP) is the first argument to the function, 8(FP) is the second (on a 64-bit machine), and so on. However, when referring to a function argument this way, it is necessary to place a name at the beginning, as in first_arg+0(FP) and second_arg+8(FP).

LEAQ buf+0(FP), BX則是擷取到第一個參數的儲存地址,而根據golang的堆棧布局,這個地址其實是調用者的sp,如下:

接下來的幾句比較容易理解,在第一句擷取了gobuf的地址後,接下來將一些相關成員設定成合適的value。 最關鍵的是以下幾句

get_tls(CX)MOVQ    g(CX), BX MOVQ BX, gobuf_g(AX)

這幾句的作用是從TLS中擷取當前線程啟動並執行g,然後將其儲存在gobuf的成員g。

gogo()

gogo的作用正好相反,用來從gobuf中恢複出協程執行狀態並跳轉到上一次指令處繼續執行。因此,其代碼也相對比較容易理解,我們就不過多贅述,如下:

gogo()主要的調用路徑:schedule()–>execute()–>googo()

// void gogo(Gobuf*)// restore state from Gobuf; longjmp TEXT runtime·gogo(SB), NOSPLIT, $0-8 MOVQ    buf+0(FP), BX           // gobufMOVQ    gobuf_g(BX), DX MOVQ 0(DX), CX get_tls(CX)MOVQ DX, g(CX)MOVQ    gobuf_sp(BX), SP        // restore SP MOVQ    gobuf_ret(BX), AX MOVQ    gobuf_ctxt(BX), DX MOVQ    gobuf_bp(BX), BP MOVQ $0, gobuf_sp(BX)MOVQ $0, gobuf_ret(BX)MOVQ $0, gobuf_ctxt(BX)MOVQ $0, gobuf_bp(BX)// 恢複出上一次執行指令,並跳轉至該指令處MOVQ    gobuf_pc(BX), BX JMP BX 

這裡最後一句跳轉至該協程被調度出的那條語句繼續執行,需要注意的是該函數不再返回調用者。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.