Go语言 goroutine 调度器常用结构体

 

调度器结构体的定义全部位于 Go 语言的源代码路径下的 runtime/runtime2.go 文件之中。

 

1. stack 结构体

stack结构体主要用来记录goroutine所使用的栈的信息,包括栈顶和栈底位置:

// Stack describes a Go execution stack.
// The bounds of the stack are exactly [lo, hi),
// with no implicit data structures on either side.
//用于记录goroutine使用的栈的起始和结束位置
type stack struct {  
    lo uintptr   // 栈顶,指向内存低地址
    hi uintptr   // 栈底,指向内存高地址
}

2. gobuf 结构体

gobuf结构体用于保存goroutine的调度信息,主要包括CPU的几个寄存器的值:

type gobuf struct {
    // The offsets of sp, pc, and g are known to (hard-coded in) libmach.
    //
    // ctxt is unusual with respect to GC: it may be a
    // heap-allocated funcval, so GC needs to track it, but it
    // needs to be set and cleared from assembly, where it's
    // difficult to have write barriers. However, ctxt is really a
    // saved, live register, and we only ever exchange it between
    // the real register and the gobuf. Hence, we treat it as a
    // root during stack scanning, which means assembly that saves
    // and restores it doesn't need write barriers. It's still
    // typed as a pointer so that any other writes from Go get
    // write barriers.
    sp  uintptr // 保存CPU的rsp寄存器的值
    pc  uintptr // 保存CPU的rip寄存器的值
    g   guintptr// 记录当前这个gobuf对象属于哪个goroutine
    ctxt unsafe.Pointer
 
   // 保存系统调用的返回值,因为从系统调用返回之后如果p被其它工作线程抢占,
   // 则这个goroutine会被放入全局运行队列被其它工作线程调度,其它线程需要知道系统调用的返回值。
    ret sys.Uintreg 
    lr  uintptr
 
    // 保存CPU的rip寄存器的值
    bp  uintptr// for GOEXPERIMENT=framepointer
}

3. g 结构体

g结构体用于代表一个goroutine,该结构体保存了goroutine的所有信息,包括栈,gobuf结构体和其它的一些状态信息:

// 前文所说的g结构体,它代表了一个goroutine
type g struct {
    // Stack parameters.
    // stack describes the actual stack memory: [stack.lo, stack.hi).
    // stackguard0 is the stack pointer compared in the Go stack growth prologue.
    // It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.
    // stackguard1 is the stack pointer compared in the C stack growth prologue.
    // It is stack.lo+StackGuard on g0 and gsignal stacks.
    // It is ~0 on other goroutine stacks, to trigger a call to morestackc (and crash).
 
    // 记录该goroutine使用的栈
    stack      stack  // offset known to runtime/cgo
    // 下面两个成员用于栈溢出检查,实现栈的自动伸缩,抢占调度也会用到stackguard0
    stackguard0 uintptr // offset known to liblink
    stackguard1 uintptr // offset known to liblink

    ......
 
    // 此goroutine正在被哪个工作线程执行
    m             *m     // current m; offset known to arm liblink
    // 保存调度信息,主要是几个寄存器的值
    sched         gobuf
 
    ......
    // schedlink字段指向全局运行队列中的下一个g,
    // 所有位于全局运行队列中的g形成一个链表
    schedlink     guintptr

    ......
    // 抢占调度标志,如果需要抢占调度,设置preempt为true
    preempt       bool      // preemption signal, duplicates stackguard0 = stackpreempt

   ......
}

4. m 结构体

m结构体用来代表工作线程,它保存了m自身使用的栈信息,当前正在运行的goroutine以及与m绑定的p等信息,详见下面定义中的注释:

type m struct{
    // g0主要用来记录工作线程使用的栈信息,在执行调度代码时需要使用这个栈
    // 执行用户goroutine代码时,使用用户goroutine自己的栈,调度时会发生栈的切换
    g0     *g    // goroutine with scheduling stack

    // 通过TLS实现m结构体对象与工作线程之间的绑定
    tls             [6]uintptr  // thread-local storage (for x86 extern register)
    mstartfn    func()
    // 指向工作线程正在运行的goroutine的g结构体对象
    curg         *g      // current running goroutine
 
    // 记录与当前工作线程绑定的p结构体对象
    p            puintptr// attached p for executing go code (nil if not executing go code)
    nextp      puintptr
    oldp        puintptr// the p that was attached before executing a syscall
   
    // spinning状态:表示当前工作线程正在试图从其它工作线程的本地运行队列偷取goroutine
    spinning     bool// m is out of work and is actively looking for work
    blocked      bool// m is blocked on a note
   
    // 没有goroutine需要运行时,工作线程睡眠在这个park成员上,
    // 其它线程通过这个park唤醒该工作线程
    park         note
    // 记录所有工作线程的一个链表
    alllink      *m// on allm
    schedlink    muintptr

    // Linux平台thread的值就是操作系统线程ID
    thread       uintptr// thread handle
    freelink     *m     // on sched.freem

    ......
}

5. p 结构体

p结构体用于保存工作线程执行go代码时所必需的资源,比如goroutine的运行队列,内存分配用到的缓存等等。

type p struct {
    lock mutex

    status     uint32// one of pidle/prunning/...
    link           puintptr
    schedtick  uint32    // incremented on every scheduler call
    syscalltick  uint32    // incremented on every system call
    sysmontick sysmontick// last tick observed by sysmon
    m               muintptr  // back-link to associated m (nil if idle)

    ......

    // Queue of runnable goroutines. Accessed without lock.
    //本地goroutine运行队列
    runqhead  uint32       // 队列头
    runqtail    uint32         // 队列尾
    runq        [256]guintptr //使用数组实现的循环队列
    // runnext, if non-nil, is a runnable G that was ready'd by
    // the current G and should be run next instead of what's in
    // runq if there's time remaining in the running G's time
    // slice. It will inherit the time left in the current time
    // slice. If a set of goroutines is locked in a
    // communicate-and-wait pattern, this schedules that set as a
    // unit and eliminates the (potentially large) scheduling
    // latency that otherwise arises from adding the ready'd
    // goroutines to the end of the run queue.
    runnextg uintptr

    // Available G's (status == Gdead)
    gFree struct{
        gList
        nint32
    }

    ......
}

6. schedt结构体

schedt结构体用来保存调度器的状态信息和goroutine的全局运行队列:

type schedt struct {
    // accessed atomically. keep at top to ensure alignment on 32-bit systems.
    goidgen uint64
    lastpoll  uint64

    lock mutex

    // When increasing nmidle, nmidlelocked, nmsys, or nmfreed, be
    // sure to call checkdead().

    // 由空闲的工作线程组成链表
    midle       muintptr// idle m's waiting for work
    // 空闲的工作线程的数量
    nmidle      int32   // number of idle m's waiting for work
    nmidlelockedint32   // number of locked m's waiting for work
    mnext       int64   // number of m's that have been created and next M ID
    // 最多只能创建maxmcount个工作线程
    maxmcount   int32   // maximum number of m's allowed (or die)
    nmsys       int32   // number of system m's not counted for deadlock
    nmfreed     int64   // cumulative number of freed m's

    ngsys uint32// number of system goroutines; updated atomically

    // 由空闲的p结构体对象组成的链表
    pidle     puintptr// idle p's
    // 空闲的p结构体对象的数量
    npidle    uint32
    nmspinning uint32// See "Worker thread parking/unparking" comment in proc.go.

    // Global runnable queue.
    // goroutine全局运行队列
    runq    gQueue
    runqsize int32

    ......

    // Global cache of dead G's.
    // gFree是所有已经退出的goroutine对应的g结构体对象组成的链表
    // 用于缓存g结构体对象,避免每次创建goroutine时都重新分配内存
    gFree struct{
        lock         mutex
        stack       gList // Gs with stacks
        noStack   gList // Gs without stacks
        n             int32
    }
 
    ......
}

7. 重要的全局变量

allgs    []*g     // 保存所有的g
allm      *m     // 所有的m构成的一个链表,包括下面的m0
allp      []*p    // 保存所有的p,len(allp) == gomaxprocs

ncpu             int32  // 系统中cpu核的数量,程序启动时由runtime代码初始化
gomaxprocs  int32   // p的最大值,默认等于ncpu,但可以通过GOMAXPROCS修改

sched     schedt    // 调度器结构体对象,记录了调度器的工作状态

m0 m      // 代表进程的主线程
g0  g       // m0的g0,也就是m0.g0 = &g0

在程序初始化时,这些全变量都会被初始化为0值,指针会被初始化为nil指针,切片初始化为nil切片,int被初始化为数字0,结构体的所有成员变量按其本类型初始化为其类型的0值。所以程序刚启动时allgs,allm和allp都不包含任何g,m和p。

我们以 Hello World 程序为例,通过跟踪其从启动到退出这一完整的运行流程来分析 Go 语言调度器的初始化、goroutine的创建与退出、工作线程的调度循环以及 goroutine 的切换等重要内 ...