goroutine调度源码

本文阅读 5 分钟
首页 golang 正文
题目序号:(2807)
题目来源: 陌陌
频次: 1

答案: 小强

G、P、M 是 Go 调度器的三个核心组件,各司其职。在它们精密地配合下,Go 调度器得以高效运转,这也是Go天然支持高并发的内在动力。今天这篇文章我们来深入理解 GPM 模型。

先看 G,取 goroutine 的首字母,主要保存 goroutine 的一些状态信息以及 CPU 的一些寄存器的值,例如 IP 寄存器,以便在轮到本 goroutine 执行时,CPU 知道要从哪一条指令处开始执行。
runtime.g 的源码如下:

type g struct {

    // goroutine 使用的栈
    stack stack
    // offset known to runtime/cgo
    // 用于栈的扩张和收缩检查,抢占标志
    stackguard0 uintptr
    // offset known to liblink

    stackguard1 uintptr
    // offset known to liblink
    _panic *_panic
    // innermost panic - offset known to liblink
    _defer *_defer

    // 当前与 g 绑定的 m
    m *m
    // current m; offset known to arm liblink
    // goroutine 的运行现场
    sched gobuf

    syscallsp uintptr
    // if status==Gsyscall, syscallsp = sched.sp to use during gc

    syscallpc uintptr
    // if status==Gsyscall, syscallpc = sched.pc to use during gc

    stktopsp uintptr
    // expected sp at top of stack, to check in traceback

    // wakeup 时传入的参数
    param unsafe.Pointer

    // passed parameter on wakeup

    atomicstatus uint32

    stackLock uint32
    // sigprof/scang lock;
    goid int64

    // g 被阻塞之后的近似时间
    waitsince int64
    // approx time when the g become blocked

    // g 被阻塞的原因
    waitreason string

    // 指向全局队列里下一个 g
    schedlink guintptr

    // 抢占调度标志。这个为 true 时,stackguard0 等于 stackpreempt
    preempt bool

    // preemption signal, duplicates stackguard0 = stackpreempt
    paniconfault bool

    // panic (instead of crash) on unexpected fault address
    preemptscan bool

    // preempted g does scan for gc
    gcscandone bool

    // g has scanned stack; protected by _Gscan bit in status
    gcscanvalid bool
    // false at start of gc cycle, true if G has not run since last scan;
    throwsplit bool

    // must not split stack
    raceignore int8
    // ignore race detection events
    sysblocktraced bool

    // StartTrace has emitted EvGoInSyscall about this goroutine

    // syscall 返回之后的 cputicks,用来做 tracing

    sysexitticks int64
    // cputicks when syscall has returned (for tracing)

    traceseq uint64
    // trace event sequencer
    tracelastp puintptr
    // last P emitted an event for this goroutine
    // 如果调用了 LockOsThread,那么这个 g 会绑定到某个 m 上
    lockedm *m
    sig uint32
    writebuf []byte

    sigcode0 uintptr

    sigcode1 uintptr

    sigpc uintptr
    // 创建该 goroutine 的语句的指令地址

    gopc uintptr
    // pc of go statement that created this goroutine
    // goroutine 函数的指令地址

    startpc uintptr
    // pc of goroutine function

    racectx uintptr

    waiting *sudog
    // sudog structures this g is waiting on (that have a valid elem ptr); in lock order

    cgoCtxt []uintptr
    // cgo traceback context

    labels unsafe.Pointer
    // profiler labels

    // time.Sleep 缓存的定时器
    timer *timer
    // cached timer for time.Sleep

    gcAssistBytes int64
}

再来看 M,取 machine 的首字母,它代表一个工作线程,或者说系统线程。G 需要调度到 M 上才能运行,M 是真正工作的人。结构体 m 就是我们常说的 M,它保存了 M 自身使用的栈信息、当前正在 M 上执行的 G 信息、与之绑定的 P 信息……

当 M 没有工作可做的时候,在它休眠前,会“自旋”地来找工作:检查全局队列,查看 network poller,试图执行 gc 任务,或者“偷”工作。

结构体 m 的源码如下:

type m struct {
    // 记录工作线程(也就是内核线程)使用的栈信息。在执行调度代码时需要使用
    // 执行用户 goroutine 代码时,使用用户 goroutine 自己的栈,因此调度时会发生栈的切换
    g0 *g
    // goroutine with scheduling stack/
    morebuf gobuf
    // gobuf arg to morestack

    divmod uint32
    // div/mod denominator for arm - known to liblink
    // Fields not known to debuggers.

    procid uint64
    // for debuggers, but offset not hard-coded

    gsignal *g
    // signal-handling g

    sigmask sigset
    // storage for saved signal mask
    // 通过 tls 结构体实现 m 与工作线程的绑定
    // 这里是线程本地存储
    tls [6]uintptr
    // thread-local storage (for x86 extern register)
    mstartfn func()

    // 指向正在运行的 gorutine 对象
    curg *g
    // current running goroutine
    caughtsig guintptr
    // goroutine running during fatal signal

    // 当前工作线程绑定的 p
    p puintptr
    // attached p for executing go code (nil if not executing go code)
    nextp puintptr

    id int32

    mallocing int32

    throwing int32

    // 该字段不等于空字符串的话,要保持 curg 始终在这个 m 上运行
    preemptoff string
    // if != "", keep curg running on this m
    locks int32

    softfloat int32

    dying int32

    profilehz int32

    helpgc int32

    // 为 true 时表示当前 m 处于自旋状态,正在从其他线程偷工作
    spinning bool

    // m is out of work and is actively looking for work
    // m 正阻塞在 note 上
    blocked bool

    // m is blocked on a note
    // m 正在执行 write barrier
    inwb bool

    // m is executing a write barrier
    newSigstack bool

    // minit on C thread called sigaltstack
    printlock int8

    // 正在执行 cgo 调用
    incgo bool

    // m is executing a cgo call
    fastrand uint32

    // cgo 调用总计数
    ncgocall uint64
    // number of cgo calls in total

    ncgo int32
    // number of cgo calls currently in progress

    cgoCallersUse uint32
    // if non-zero, cgoCallers in use temporarily

    cgoCallers *cgoCallers
    // cgo traceback if crashing in cgo call

    // 没有 goroutine 需要运行时,工作线程睡眠在这个 park 成员上,

    // 其它线程通过这个 park 唤醒该工作线程
    park note

    // 记录所有工作线程的链表
    alllink *m
    // on allm

    schedlink muintptr

    mcache *mcache

    lockedg *g

    createstack [32]uintptr
    // stack that created this thread.

    freglo [16]uint32
    // d[i] lsb and f[i]

    freghi [16]uint32
    // d[i] msb and f[i+16]

    fflag uint32
    // floating point compare flags

    locked uint32
    // tracking for lockosthread

    // 正在等待锁的下一个 m

    nextwaitm uintptr
    // next m waiting for lock

    needextram bool

    traceback uint8

    waitunlockf unsafe.Pointer
    // todo go func(*g, unsafe.pointer) bool

    waitlock unsafe.Pointer

    waittraceev byte

    waittraceskip int

    startingtrace bool

    syscalltick uint32

    // 工作线程 id
    thread uintptr
    // thread handle

    // these are here because they are too large to be on the stack

    // of low-level NOSPLIT functions.

    libcall libcall

    libcallpc uintptr
    // for cpu profiler

    libcallsp uintptr

    libcallg guintptr

    syscall libcall
    // stores syscall parameters on windows

    mOS
}

再来看 P,取 processor 的首字母,为 M 的执行提供“上下文”,保存 M 执行 G 时的一些资源,例如本地可运行 G 队列,memeory cache 等。

一个 M 只有绑定 P 才能执行 goroutine,当 M 被阻塞时,整个 P 会被传递给其他 M ,或者说整个 P 被接管。

// p 保存 go 运行时所必须的资源

type p struct {
    lock mutex
    // 在 allp 中的索引
    id int32

    status uint32
    // one of pidle/prunning/...

    link puintptr

    // 每次调用 schedule 时会加一
    schedtick uint32

    // 每次系统调用时加一
    syscalltick uint32

    // 用于 sysmon 线程记录被监控 p 的系统调用时间和运行时间
    sysmontick sysmontick
    // last tick observed by sysmon

    // 指向绑定的 m,如果 p 是 idle 的话,那这个指针是 nil
    m muintptr
    // back-link to associated m (nil if idle)
    mcache  *mcache
    racectx uintptr

    deferpool [5][]*_defer
    // pool of available defer structs of different sizes (see panic.go)
    deferpoolbuf [5][32]*_defer

    // Cache of goroutine ids, amortizes accesses to runtime·sched.goidgen.
    goidcache    uint64
    goidcacheend uint64

    // Queue of runnable goroutines. Accessed without lock.
    // 本地可运行的队列,不用通过锁即可访问
    runqhead uint32
    // 队列头
    runqtail uint32
    // 队列尾
    // 使用数组实现的循环队列
    runq [256]guintptr

    // runnext 非空时,代表的是一个 runnable 状态的 G,
    // 这个 G 被 当前 G 修改为 ready 状态,相比 runq 中的 G 有更高的优先级。
    // 如果当前 G 还有剩余的可用时间,那么就应该运行这个 G
    // 运行之后,该 G 会继承当前 G 的剩余时间
    runnext guintptr

    // Available G's (status == Gdead)
    // 空闲的 g
    gfree    *g
    gfreecnt int32

    sudogcache []*sudog
    sudogbuf   [128]*sudog

    tracebuf                   traceBufPtr
    traceSwept, traceReclaimed uintptr
    palloc                     persistentAlloc
    // per-P to avoid mutex
    // Per-P GC state
    gcAssistTime int64
    // Nanoseconds in assistAlloc

    gcBgMarkWorker guintptr

    gcMarkWorkerMode gcMarkWorkerMode

    runSafePointFn uint32
    // if 1, run sched.safePointFn at next safe point
    pad [sys.CacheLineSize]byte
}
本文来自投稿,不代表本站立场,如若转载,请注明出处:
syncpool的实现原理
« 上一篇 09-17
问了sync.Map(我说我对sync.Pool比较熟,就说Pool了)
下一篇 » 09-17

发表评论

发表评论