goroutine调度源码

参考解析

题目来源: 陌陌

答案:

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

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

  1. type g struct {
  2. // goroutine 使用的栈
  3. stack stack
  4. // offset known to runtime/cgo
  5. // 用于栈的扩张和收缩检查,抢占标志
  6. stackguard0 uintptr
  7. // offset known to liblink
  8. stackguard1 uintptr
  9. // offset known to liblink
  10. _panic *_panic
  11. // innermost panic - offset known to liblink
  12. _defer *_defer
  13. // 当前与 g 绑定的 m
  14. m *m
  15. // current m; offset known to arm liblink
  16. // goroutine 的运行现场
  17. sched gobuf
  18. syscallsp uintptr
  19. // if status==Gsyscall, syscallsp = sched.sp to use during gc
  20. syscallpc uintptr
  21. // if status==Gsyscall, syscallpc = sched.pc to use during gc
  22. stktopsp uintptr
  23. // expected sp at top of stack, to check in traceback
  24. // wakeup 时传入的参数
  25. param unsafe.Pointer
  26. // passed parameter on wakeup
  27. atomicstatus uint32
  28. stackLock uint32
  29. // sigprof/scang lock;
  30. goid int64
  31. // g 被阻塞之后的近似时间
  32. waitsince int64
  33. // approx time when the g become blocked
  34. // g 被阻塞的原因
  35. waitreason string
  36. // 指向全局队列里下一个 g
  37. schedlink guintptr
  38. // 抢占调度标志。这个为 true 时,stackguard0 等于 stackpreempt
  39. preempt bool
  40. // preemption signal, duplicates stackguard0 = stackpreempt
  41. paniconfault bool
  42. // panic (instead of crash) on unexpected fault address
  43. preemptscan bool
  44. // preempted g does scan for gc
  45. gcscandone bool
  46. // g has scanned stack; protected by _Gscan bit in status
  47. gcscanvalid bool
  48. // false at start of gc cycle, true if G has not run since last scan;
  49. throwsplit bool
  50. // must not split stack
  51. raceignore int8
  52. // ignore race detection events
  53. sysblocktraced bool
  54. // StartTrace has emitted EvGoInSyscall about this goroutine
  55. // syscall 返回之后的 cputicks,用来做 tracing
  56. sysexitticks int64
  57. // cputicks when syscall has returned (for tracing)
  58. traceseq uint64
  59. // trace event sequencer
  60. tracelastp puintptr
  61. // last P emitted an event for this goroutine
  62. // 如果调用了 LockOsThread,那么这个 g 会绑定到某个 m 上
  63. lockedm *m
  64. sig uint32
  65. writebuf []byte
  66. sigcode0 uintptr
  67. sigcode1 uintptr
  68. sigpc uintptr
  69. // 创建该 goroutine 的语句的指令地址
  70. gopc uintptr
  71. // pc of go statement that created this goroutine
  72. // goroutine 函数的指令地址
  73. startpc uintptr
  74. // pc of goroutine function
  75. racectx uintptr
  76. waiting *sudog
  77. // sudog structures this g is waiting on (that have a valid elem ptr); in lock order
  78. cgoCtxt []uintptr
  79. // cgo traceback context
  80. labels unsafe.Pointer
  81. // profiler labels
  82. // time.Sleep 缓存的定时器
  83. timer *timer
  84. // cached timer for time.Sleep
  85. gcAssistBytes int64
  86. }

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

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

结构体 m 的源码如下:

  1. type m struct {
  2. // 记录工作线程(也就是内核线程)使用的栈信息。在执行调度代码时需要使用
  3. // 执行用户 goroutine 代码时,使用用户 goroutine 自己的栈,因此调度时会发生栈的切换
  4. g0 *g
  5. // goroutine with scheduling stack/
  6. morebuf gobuf
  7. // gobuf arg to morestack
  8. divmod uint32
  9. // div/mod denominator for arm - known to liblink
  10. // Fields not known to debuggers.
  11. procid uint64
  12. // for debuggers, but offset not hard-coded
  13. gsignal *g
  14. // signal-handling g
  15. sigmask sigset
  16. // storage for saved signal mask
  17. // 通过 tls 结构体实现 m 与工作线程的绑定
  18. // 这里是线程本地存储
  19. tls [6]uintptr
  20. // thread-local storage (for x86 extern register)
  21. mstartfn func()
  22. // 指向正在运行的 gorutine 对象
  23. curg *g
  24. // current running goroutine
  25. caughtsig guintptr
  26. // goroutine running during fatal signal
  27. // 当前工作线程绑定的 p
  28. p puintptr
  29. // attached p for executing go code (nil if not executing go code)
  30. nextp puintptr
  31. id int32
  32. mallocing int32
  33. throwing int32
  34. // 该字段不等于空字符串的话,要保持 curg 始终在这个 m 上运行
  35. preemptoff string
  36. // if != "", keep curg running on this m
  37. locks int32
  38. softfloat int32
  39. dying int32
  40. profilehz int32
  41. helpgc int32
  42. // 为 true 时表示当前 m 处于自旋状态,正在从其他线程偷工作
  43. spinning bool
  44. // m is out of work and is actively looking for work
  45. // m 正阻塞在 note 上
  46. blocked bool
  47. // m is blocked on a note
  48. // m 正在执行 write barrier
  49. inwb bool
  50. // m is executing a write barrier
  51. newSigstack bool
  52. // minit on C thread called sigaltstack
  53. printlock int8
  54. // 正在执行 cgo 调用
  55. incgo bool
  56. // m is executing a cgo call
  57. fastrand uint32
  58. // cgo 调用总计数
  59. ncgocall uint64
  60. // number of cgo calls in total
  61. ncgo int32
  62. // number of cgo calls currently in progress
  63. cgoCallersUse uint32
  64. // if non-zero, cgoCallers in use temporarily
  65. cgoCallers *cgoCallers
  66. // cgo traceback if crashing in cgo call
  67. // 没有 goroutine 需要运行时,工作线程睡眠在这个 park 成员上,
  68. // 其它线程通过这个 park 唤醒该工作线程
  69. park note
  70. // 记录所有工作线程的链表
  71. alllink *m
  72. // on allm
  73. schedlink muintptr
  74. mcache *mcache
  75. lockedg *g
  76. createstack [32]uintptr
  77. // stack that created this thread.
  78. freglo [16]uint32
  79. // d[i] lsb and f[i]
  80. freghi [16]uint32
  81. // d[i] msb and f[i+16]
  82. fflag uint32
  83. // floating point compare flags
  84. locked uint32
  85. // tracking for lockosthread
  86. // 正在等待锁的下一个 m
  87. nextwaitm uintptr
  88. // next m waiting for lock
  89. needextram bool
  90. traceback uint8
  91. waitunlockf unsafe.Pointer
  92. // todo go func(*g, unsafe.pointer) bool
  93. waitlock unsafe.Pointer
  94. waittraceev byte
  95. waittraceskip int
  96. startingtrace bool
  97. syscalltick uint32
  98. // 工作线程 id
  99. thread uintptr
  100. // thread handle
  101. // these are here because they are too large to be on the stack
  102. // of low-level NOSPLIT functions.
  103. libcall libcall
  104. libcallpc uintptr
  105. // for cpu profiler
  106. libcallsp uintptr
  107. libcallg guintptr
  108. syscall libcall
  109. // stores syscall parameters on windows
  110. mOS
  111. }

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

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

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

  1. type p struct {
  2. lock mutex
  3. // 在 allp 中的索引
  4. id int32
  5. status uint32
  6. // one of pidle/prunning/...
  7. link puintptr
  8. // 每次调用 schedule 时会加一
  9. schedtick uint32
  10. // 每次系统调用时加一
  11. syscalltick uint32
  12. // 用于 sysmon 线程记录被监控 p 的系统调用时间和运行时间
  13. sysmontick sysmontick
  14. // last tick observed by sysmon
  15. // 指向绑定的 m,如果 p 是 idle 的话,那这个指针是 nil
  16. m muintptr
  17. // back-link to associated m (nil if idle)
  18. mcache *mcache
  19. racectx uintptr
  20. deferpool [5][]*_defer
  21. // pool of available defer structs of different sizes (see panic.go)
  22. deferpoolbuf [5][32]*_defer
  23. // Cache of goroutine ids, amortizes accesses to runtime·sched.goidgen.
  24. goidcache uint64
  25. goidcacheend uint64
  26. // Queue of runnable goroutines. Accessed without lock.
  27. // 本地可运行的队列,不用通过锁即可访问
  28. runqhead uint32
  29. // 队列头
  30. runqtail uint32
  31. // 队列尾
  32. // 使用数组实现的循环队列
  33. runq [256]guintptr
  34. // runnext 非空时,代表的是一个 runnable 状态的 G,
  35. // 这个 G 被 当前 G 修改为 ready 状态,相比 runq 中的 G 有更高的优先级。
  36. // 如果当前 G 还有剩余的可用时间,那么就应该运行这个 G
  37. // 运行之后,该 G 会继承当前 G 的剩余时间
  38. runnext guintptr
  39. // Available G's (status == Gdead)
  40. // 空闲的 g
  41. gfree *g
  42. gfreecnt int32
  43. sudogcache []*sudog
  44. sudogbuf [128]*sudog
  45. tracebuf traceBufPtr
  46. traceSwept, traceReclaimed uintptr
  47. palloc persistentAlloc
  48. // per-P to avoid mutex
  49. // Per-P GC state
  50. gcAssistTime int64
  51. // Nanoseconds in assistAlloc
  52. gcBgMarkWorker guintptr
  53. gcMarkWorkerMode gcMarkWorkerMode
  54. runSafePointFn uint32
  55. // if 1, run sched.safePointFn at next safe point
  56. pad [sys.CacheLineSize]byte
  57. }