- 协程调度, 内存分配, 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频繁, 编解码效率低等.