Go 的线程实现模型,有三个核心的元素 M、P、G,它们共同支撑起了这个线程模型的框架。其中,G 是 goroutine 的缩写,通常称为 “协程”。关于协程、线程和进程三者的异同,可以参照 “进程、线程和协程的区别”。

每一个 Goroutine 在程序运行期间,都会对应分配一个 g 结构体对象。g 中存储着 Goroutine 的运行堆栈、状态以及任务函数,g 结构的定义位于 src/runtime/runtime2.go 文件中。

g 对象可以重复使用,当一个 goroutine 退出时,g 对象会被放到一个空闲的 g 对象池中以用于后续的 goroutine 的使用,以减少内存分配开销。

1. Goroutine 字段注释

g 字段非常的多,我们这里分段来理解:

  1. type g struct {
  2. // Stack parameters.
  3. // stack describes the actual stack memory: [stack.lo, stack.hi).
  4. // stackguard0 is the stack pointer compared in the Go stack growth prologue.
  5. // It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.
  6. // stackguard1 is the stack pointer compared in the C stack growth prologue.
  7. // It is stack.lo+StackGuard on g0 and gsignal stacks.
  8. // It is ~0 on other goroutine stacks, to trigger a call to morestackc (and crash).
  9. stack stack // offset known to runtime/cgo
  10.  
  11. // 检查栈空间是否足够的值, 低于这个值会扩张, stackguard0 供 Go 代码使用
  12. stackguard0 uintptr // offset known to liblink
  13.  
  14. // 检查栈空间是否足够的值, 低于这个值会扩张, stackguard1 供 C 代码使用
  15. stackguard1 uintptr // offset known to liblink
  16. }

stack 描述了当前 goroutine 的栈内存范围[stack.lo, stack.hi),其中 stack 的数据结构:

  1. // Stack describes a Go execution stack.
  2. // The bounds of the stack are exactly [lo, hi),
  3. // with no implicit data structures on either side.
  4. // 描述 goroutine 执行栈
  5. // 栈边界为[lo, hi),左包含右不包含,即 lo≤stack<hi
  6. // 两边都没有隐含的数据结构。
  7. type stack struct {
  8. lo uintptr // 该协程拥有的栈低位
  9. hi uintptr // 该协程拥有的栈高位
  10. }

stackguard0 和 stackguard1 均是一个栈指针,用于扩容场景,前者用于 Go stack ,后者用于 C stack。

如果 stackguard0 字段被设置成 StackPreempt,意味着当前 Goroutine 发出了抢占请求。

g结构体中的stackguard0 字段是出现爆栈前的警戒线。stackguard0 的偏移量是16个字节,与当前的真实SP(stack pointer)和爆栈警戒线(stack.lo+StackGuard)比较,如果超出警戒线则表示需要进行栈扩容。先调用runtime·morestack_noctxt()进行栈扩容,然后又跳回到函数的开始位置,此时此刻函数的栈已经调整了。然后再进行一次栈大小的检测,如果依然不足则继续扩容,直到栈足够大为止。

  1. type g struct {
  2. preempt bool // preemption signal, duplicates stackguard0 = stackpreempt
  3. preemptStop bool // transition to _Gpreempted on preemption; otherwise, just deschedule
  4. preemptShrink bool // shrink stack at synchronous safe point
  5. }
  • preempt 抢占标记,其值为 true 执行 stackguard0 = stackpreempt。
  • preemptStop 将抢占标记修改为 _Gpreedmpted,如果修改失败则取消。
  • preemptShrink 在同步安全点收缩栈。
  1. type g struct {
  2. _panic *_panic // innermost panic - offset known to liblink
  3. _defer *_defer // innermost defer
  4. }
  • _panic 当前Goroutine 中的 panic。
  • _defer 当前Goroutine 中的 defer。
  1. type g struct {
  2. m *m // current m; offset known to arm liblink
  3. sched gobuf
  4. goid int64
  5. }
  • m 当前 Goroutine 绑定的 M。
  • sched 存储当前 Goroutine 调度相关的数据,上下方切换时会把当前信息保存到这里,用的时候再取出来。
  • goid 当前 Goroutine 的唯一标识,对开发者不可见,一般不使用此字段,Go 开发团队未向外开放访问此字段。

gobuf 结构体定义:

  1. type gobuf struct {
  2. // The offsets of sp, pc, and g are known to (hard-coded in) libmach.
  3. // 寄存器 sp, pc 和 g 的偏移量,硬编码在 libmach
  4. //
  5. // ctxt is unusual with respect to GC: it may be a
  6. // heap-allocated funcval, so GC needs to track it, but it
  7. // needs to be set and cleared from assembly, where it's
  8. // difficult to have write barriers. However, ctxt is really a
  9. // saved, live register, and we only ever exchange it between
  10. // the real register and the gobuf. Hence, we treat it as a
  11. // root during stack scanning, which means assembly that saves
  12. // and restores it doesn't need write barriers. It's still
  13. // typed as a pointer so that any other writes from Go get
  14. // write barriers.
  15. sp uintptr
  16. pc uintptr
  17. g guintptr
  18. ctxt unsafe.Pointer
  19. ret sys.Uintreg
  20. lr uintptr
  21. bp uintptr // for GOEXPERIMENT=framepointer
  22. }
  • sp 栈指针位置。
  • pc 程序计数器,运行到的程序位置。

ctxt

    不常见,可能是一个分配在heap的函数变量,因此GC 需要追踪它,不过它有可能需要设置并进行清除,在有

写屏障

    的时候有些困难。重点了解一下

write barriers

  • g 当前 gobuf 的 Goroutine。
  • ret 系统调用的结果。

调度器在将 G 由一种状态变更为另一种状态时,需要将上下文信息保存到这个gobuf结构体,当再次运行 G 的时候,再从这个结构体中读取出来,它主要用来暂存上下文信息。其中的栈指针 sp 和程序计数器 pc 会用来存储或者恢复寄存器中的值,设置即将执行的代码。

2. Goroutine 状态种类

Goroutine 的状态有以下几种:

状态 描述
_Gidle 0 刚刚被分配并且还没有被初始化
_Grunnable 1 没有执行代码,没有栈的所有权,存储在运行队列中
_Grunning 2 可以执行代码,拥有栈的所有权,被赋予了内核线程 M 和处理器 P
_Gsyscall 3 正在执行系统调用,没有执行用户代码,拥有栈的所有权,被赋予了内核线程 M 但是不在运行队列上
_Gwaiting 4 由于运行时而被阻塞,没有执行用户代码并且不在运行队列上,但是可能存在于 Channel 的等待队列上。若需要时执行ready()唤醒。
_Gmoribund_unused 5 当前此状态未使用,但硬编码在了gdb 脚本里,可以不用关注
_Gdead 6 没有被使用,可能刚刚退出,或在一个freelist;也或者刚刚被初始化;没有执行代码,可能有分配的栈也可能没有;G和分配的栈(如果已分配过栈)归刚刚退出G的M所有或从free list 中获取
_Genqueue_unused 7 目前未使用,不用理会
_Gcopystack 8 栈正在被拷贝,没有执行代码,不在运行队列上
_Gpreempted 9 由于抢占而被阻塞,没有执行用户代码并且不在运行队列上,等待唤醒
_Gscan 10 GC 正在扫描栈空间,没有执行代码,可以与其他状态同时存在

需要注意的是对于 _Gmoribund_unused 状态并未使用,但在 gdb 脚本中存在;而对于 _Genqueue_unused 状态目前也未使用,不需要关心。

_Gscan 与上面除了_Grunning 状态以外的其它状态相组合,表示 GC 正在扫描栈。Goroutine 不会执行用户代码,且栈由设置了 _Gscan 位的 Goroutine 所有。

状态 描述
_Gscanrunnable = _Gscan + _Grunnable // 0x1001
_Gscanrunning = _Gscan + _Grunning // 0x1002
_Gscansyscall = _Gscan + _Gsyscall // 0x1003
_Gscanwaiting = _Gscan + _Gwaiting // 0x1004
_Gscanpreempted = _Gscan + _Gpreempted // 0x1009

3. Goroutine 状态转换

可以看到除了上面提到的两个未使用的状态外一共有14种状态值。许多状态之间是可以进行改变的。如下图所示:

  1. type g strcut {
  2. syscallsp uintptr // if status==Gsyscall, syscallsp = sched.sp to use during gc
  3. syscallpc uintptr // if status==Gsyscall, syscallpc = sched.pc to use during gc
  4. stktopsp uintptr // expected sp at top of stack, to check in traceback
  5. param unsafe.Pointer // passed parameter on wakeup
  6. atomicstatus uint32
  7. stackLock uint32 // sigprof/scang lock; TODO: fold in to atomicstatus
  8. }
  • atomicstatus 当前 G 的状态,上面介绍过 G 的几种状态值。
  • syscallsp 如果 G 的状态为 Gsyscall,那么值为 sched.sp 主要用于GC 期间。
  • syscallpc 如果 G 的状态为 GSyscall,那么值为 sched.pc 主要用于GC 期间。由此可见这两个字段通常一起使用。
  • stktopsp 用于回源跟踪。
  • param 唤醒 G 时传入的参数,例如调用 ready()
  • stackLock 栈锁。
  1. type g struct {
  2. waitsince int64 // approx time when the g become blocked
  3. waitreason waitReason // if status==Gwaiting
  4. }
  • waitsince G 阻塞时长。
  • waitreason 阻塞原因。
  1. type g struct {
  2. // asyncSafePoint is set if g is stopped at an asynchronous
  3. // safe point. This means there are frames on the stack
  4. // without precise pointer information.
  5. asyncSafePoint bool
  6. paniconfault bool // panic (instead of crash) on unexpected fault address
  7. gcscandone bool // g has scanned stack; protected by _Gscan bit in status
  8. throwsplit bool // must not split stack
  9. }
  • asyncSafePoint 异步安全点;如果 g 在异步安全点停止则设置为true,表示在栈上没有精确的指针信息。
  • paniconfault 地址异常引起的 panic(代替了崩溃)。
  • gcscandone g 扫描完了栈,受状态 _Gscan 位保护。
  • throwsplit 不允许拆分 stack。
  1. type g struct {
  2. // activeStackChans indicates that there are unlocked channels
  3. // pointing into this goroutine's stack. If true, stack
  4. // copying needs to acquire channel locks to protect these
  5. // areas of the stack.
  6. activeStackChans bool
  7. // parkingOnChan indicates that the goroutine is about to
  8. // park on a chansend or chanrecv. Used to signal an unsafe point
  9. // for stack shrinking. It's a boolean value, but is updated atomically.
  10. parkingOnChan uint8
  11. }
  • activeStackChans 表示是否有未加锁定的 channel 指向到了 g 栈,如果为 true,那么对栈的复制需要 channal 锁来保护这些区域。
  • parkingOnChan 表示 g 是放在 chansend 还是 chanrecv。用于栈的收缩,是一个布尔值,但是原子性更新。
  1. type g struct {
  2. raceignore int8 // ignore race detection events
  3. sysblocktraced bool // StartTrace has emitted EvGoInSyscall about this goroutine
  4. sysexitticks int64 // cputicks when syscall has returned (for tracing)
  5. traceseq uint64 // trace event sequencer
  6. tracelastp puintptr // last P emitted an event for this goroutine
  7. lockedm muintptr
  8. sig uint32
  9. writebuf []byte
  10. sigcode0 uintptr
  11. sigcode1 uintptr
  12. sigpc uintptr
  13. gopc uintptr // pc of go statement that created this goroutine
  14. ancestors *[]ancestorInfo // ancestor information goroutine(s) that created this goroutine (only used if debug.tracebackancestors)
  15. startpc uintptr // pc of goroutine function
  16. racectx uintptr
  17. waiting *sudog // sudog structures this g is waiting on (that have a valid elem ptr); in lock order
  18. cgoCtxt []uintptr // cgo traceback context
  19. labels unsafe.Pointer // profiler labels
  20. timer *timer // cached timer for time.Sleep
  21. selectDone uint32 // are we participating in a select and did someone win the race?
  22. }
  • gopc 创建当前 G 的 pc。
  • startpc go func 的 pc。
  • timer 通过time.Sleep 缓存 timer。
  1. type g struct {
  2. // Per-G GC state
  3. // gcAssistBytes is this G's GC assist credit in terms of
  4. // bytes allocated. If this is positive, then the G has credit
  5. // to allocate gcAssistBytes bytes without assisting. If this
  6. // is negative, then the G must correct this by performing
  7. // scan work. We track this in bytes to make it fast to update
  8. // and check for debt in the malloc hot path. The assist ratio
  9. // determines how this corresponds to scan work debt.
  10. gcAssistBytes int64
  11. }
  • gcAssistBytes 与 GC 相关。

4. Goroutin 总结

  • 每个 G 都有自己的状态,状态保存在 atomicstatus 字段,共有十几种状态值。
  • 每个 G 在状态发生变化时,即 atomicstatus 字段值被改变时,都需要保存当前G的上下文的信息,这个信息存储在 sched 字段,其数据类型为gobuf,想理解存储的信息可以看一下这个结构体的各个字段。
  • 每个 G 都有三个与抢占有关的字段,分别为 preemptpreemptStop 和 premptShrink
  • 每个 G 都有自己的唯一id, 字段为goid,但此字段官方不推荐开发使用。
  • 每个 G 都可以最多绑定一个m,如果可能未绑定,则值为 nil。
  • 每个 G 都有自己内部的 defer 和 panic
  • G 可以被阻塞,并存储有阻塞原因,字段 waitsince 和 waitreason
  • G 可以被进行 GC 扫描,相关字段为 gcscandoneatomicstatus ( _Gscan 与上面除了_Grunning 状态以外的其它状态组合)

参考资料:

  1. go语言教程
  2. go语言深入剖析

Go语言并发模型 G源码分析的更多相关文章

  1. 并发工具CyclicBarrier源码分析及应用

      本文首发于微信公众号[猿灯塔],转载引用请说明出处 今天呢!灯塔君跟大家讲: 并发工具CyclicBarrier源码分析及应用 一.CyclicBarrier简介 1.简介 CyclicBarri ...

  2. Java并发-ConcurrentModificationException原因源码分析与解决办法

    一.异常原因与异常源码分析 对集合(List.Set.Map)迭代时对其进行修改就会出现java.util.ConcurrentModificationException异常.这里以ArrayList ...

  3. Java并发编程-ReentrantLock源码分析

    一.前言 在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到 ...

  4. Java并发编程 ReentrantLock 源码分析

    ReentrantLock 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大. 这个类主要基于AQS(Abst ...

  5. 并发工具CountDownLatch源码分析

    CountDownLatch的作用类似于Thread.join()方法,但比join()更加灵活.它可以等待多个线程(取决于实例化时声明的数量)都达到预期状态或者完成工作以后,通知其他正在等待的线程继 ...

  6. 并发编程—— FutureTask 源码分析

    1. 前言 当我们在 Java 中使用异步编程的时候,大部分时候,我们都会使用 Future,并且使用线程池的 submit 方法提交一个 Callable 对象.然后调用 Future 的 get ...

  7. 并发编程 —— Timer 源码分析

    前言 在平时的开发中,肯定需要使用定时任务,而 Java 1.3 版本提供了一个 java.util.Timer 定时任务类.今天一起来看看这个类. 1.API 介绍 Timer 相关的有 3 个类: ...

  8. Java精通并发-通过openjdk源码分析ObjectMonitor底层实现

    在我们分析synchronized关键字底层信息时,其中谈到了Monitor对象,它是由C++来实现的,那,到底它长啥样呢?我们在编写同步代码时完全木有看到该对象的存在,所以这次打算真正来瞅一下它的真 ...

  9. Java并发编程-AbstractQueuedSynchronizer源码分析

    简介 提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架.该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础.使用的方法是继承,子类通过 ...

随机推荐

  1. VMware虚拟机常见问题(针对目前我所学的而言,还会不断更新)

    VMware虚拟机常见问题(针对目前我所学的而言,还会不断更新) 自己电脑的telnet Client是否打开 在控制面板->程序->打开或关闭Windows功能 虚拟机的telnet是否 ...

  2. CF618F-Double Knapsack【结论】

    正题 题目链接:https://www.luogu.com.cn/problem/CF618F 题目大意 给出大小为\(n\),值域为\([1,n]\)的两个可重集合\(A,B\) 需要你对它们各求出 ...

  3. 独家对话阿里云函数计算负责人不瞋:你所不知道的 Serverless

    作者 | 杨丽 出品 | 雷锋网产业组 "Serverless 其实离我们并没有那么遥远". 如果你是一名互联网研发人员,那么极有可能了解并应用过 Serverless 这套技术体 ...

  4. 使用mobaXtrem显示CentOS图形

    安装环境 yum install -y xorg-x11-xauth xorg-x11-fonts-* xorg-x11-font-utils xorg-x11-fonts-Type1 \mesa-l ...

  5. Windows用cmd编译运行C程序

    在Windows环境下用命令行编译运行程序 浙江大学-C语言程序设计进阶 配置gcc 准备一个Dev-cpp 找到gcc.exe所在目录 Dev-Cpp\MinGW64\bin 地址栏右键将地址复制为 ...

  6. Linux Manual

    man 命令用来访问存储在Linux系统上的手册页面.在想要查找的工具的名称前面输入man命 令,就可以找到那个工具相应的手册条目. 手册页不是唯一的参考资料.还有另一种叫作 info 页面的信息.可 ...

  7. python和js分别在多行字符串中插入一行字符串

    问题 一个多行字符串,"asfdb;\nwesfpjoing;\nwbfliqwbefpwqufn\nasfdwe\nsafewt\nqwern\nvar\ntgwtg\n\nftwg\n& ...

  8. 2020年09月15日-项目开发-python二次处理代码文件

    Caterpillar通过将BPMN生成为solidity代码后, 我需要对solidity代码做二次处理,即在其中的特定函数中插入event类型,以便去做事件监听. 最终生成的不仅包括solidit ...

  9. UltraSoft - Alpha - Scrum Meeting 2

    Date: Apr 09th, 2020. 会议内容为完成初步的任务分工. Scrum 情况汇报 进度情况 组员 负责 昨日进度 后两日任务 CookieLau PM.后端 继续Django tuto ...

  10. UltraSoft - Beta - Scrum Meeting 5

    Date: May 21st, 2020. Scrum 情况汇报 进度情况 组员 负责 今日进度 q2l PM.后端 修复了课程通知链接的bug Liuzh 前端 暂无 Kkkk 前端 增加消息中心板 ...