• 协程调度, 内存分配, GC;
  • 操作系统及CPU相关的操作的封装(信号处理, 系统调用, 寄存器操作, 原子操作等), CGO;
  • pprof, trace, race检测的支持;
  • map, channel, string等内置类型及反射的实现.

Runtime发展历程

版本 发布时间 改进特征 GC STW时间
v1.0 2012/3 调度GM模型, GC STW 百ms级别-秒级
v1.1 2013/5 调度G-P-M模型 同上
v1.2 2013/12 实现合作式的抢占 同上
v1.3 2014/6 GC实现Mark STW, Sweep 并行.栈扩容由split stack改为复制方式的continus stack. 添加sync.Pool 百m-几百ms级别
v1.4 2014/12 Runtime移除大部分C代码; 实现准确式GC. 引入写屏障, 为1.5的并发GC做准备. 同上
v1.5 2015/8 Runtime完全移除C代码, 实现了Go的自举.
GC 并发标记清除, 三色标记法; GOMAXPROCS默认为CPU核数, go tool trace引入
10ms级别
v1.6 2016/2 1.5中一些与并发GC不协调的地方更改. 集中式的GC协调协程, 改为状态机实现 5ms级别
v1.7 2016/8 GC时由mark栈收缩改为并发, 引入dense bitmap, SSA引入 ms级
v1.8 2017/2 hybrid write barrier, 消除re-scanning stack, GC进入sub ms. defer和cgo调用开销减少一半 sub ms(18GB堆)
V1.9 2017/8 保留用于debug的rescan stack代码移除, runtime.GC, debug.SetGCPercent, and debug.FreeOSMemory等触发STW GC改为并发GC 基本同上
V1.10 2018/2 不再限制最大GOMAXPROCS(Go 1.9为1024), LockOSThread的线程在运行的G结束后可以释放. 基本同上
V1.11 2018/8 连续的arena改为稀疏索引的方式 基本同上
V1.12 2019/2 Mark Termination流程优化 Sub ms, 但几乎减少一半

GC STW时间与堆大小, 机器性能, 应用分配偏好, 对象数量均有关.

Golang调度简述

  • PMG模型, M:N调度模型.
  • 调度在计算机中是分配工作所需资源的方法. linux的调度为CPU找到可运行的线程. 而Go的调度是为 M(线程)找到P(内存, 执行票据)和可运行的G.
  • 轻量级协程G, 栈初始2KB, 调度不涉及系统调用.
  • 用户函数调用前会检查栈空间是否足够, 不够的话, 会进行栈扩容.
  • 用户代码中的协程同步造成的阻塞, 仅仅是切换协程, 而不阻塞线程.
  • 网络操作封装了epoll, 为NonBlocking模式, 未ready, 切换协程, 不阻塞线程.
  • 每个p均有local runq, 大多数时间仅与local runq无锁交互. 实现work stealing.
  • 用户协程无优先级, 基本遵循FIFO.
  • 目前(1.12), go支持协作的抢占调度, 还不支持非协作的抢占调度.

GM模型

  • 一个全局队列放待运行的g.
  • 新生成G, 阻塞的G变为待运行, M寻找可运行的G等操作都在全局队列中 操作, 需要加线程级别的锁.
  • 调度锁问题. 单一的全局调度锁(Sched.Lock)和集中的状态, 导致伸缩性下降.
  • G传递问题. 在工作线程M之间需要经常传递runnable的G, 会加大调
  • 度延迟, 并带来额外的性能损耗.
  • Per-M的内存问题. 类似TCMalloc结构的内存结构, 每个M都需要
  • memory cache和其他类型的cache(比如stack alloc), 然而实际上只
  • 有M在运行Go代码时才需要这些Per-M Cache, 阻塞在系统调用的M
  • 并不需要这些cache. 正在运行Go代码的M与进行系统调用的M的比
  • 例可能高达1:100, 这造成了很大的内存消耗.

G状态流转

G状态 说明
Gidle 0 刚刚被分配, 还没有初始化
Grunnabel 1 表示在runqueue上, 还没有被运行
Grunning 2 go协程可能在执行go代码, 不在runqueue上, 与M, P已绑定
Gsyscall 3 go协程在执行系统调用, 没执行go代码, 没有在runqueue上, 只与M绑定
Gwaiting 4 go协程被阻塞(IO, GC, chan阻塞, 锁等). 不在runqueue上, 但是一定在某个地 方, 比如channel中, 锁排队中等.
Gdead 6 协程现在没有在使用, 也许执行完, 或者在free list中, 或者正在被初始化. 可能 有stack或者没有
Gcopystack 8 栈正在复制, 此时没有go代码, 也不在runqueue上
Gscan 0x10 00 与runnable, running, syscall, waiting等状态结合, 表示GC正在扫描这个G的 栈

和Runtime相关的几类问题

  • 内存慢慢增长OOM: 结合memory inuse_space的pprof和 list, 加上源码流程即可定位出. 一直把新对象放到全局对象或 者长生命周期对象中. 比如长连接, 连接池应用或者忘记close http resp body, sql Stmt等.
  • 内存突增OOM: 如果多次分配才OOM, 可使用方法1排查. 对于一次就OOM的, 比较难抓, 可结合go无法分配内存时 throw输出的协程栈排查.比如没有校验参数, 调用者填错或恶意, 使用传过来的length 来进行make([]byte, length)用于编解码
  • 性能问题: 结合火焰图, 查看影响性能的热点部分, 进行优化: GC频繁, 编解码效率低等.