golang runtime 的理解

本贴最后更新于 1558 天前,其中的信息可能已经东海扬尘

runtime 运行时到底是个什么东西?

Go 的调度为什么说是轻量的?

Go 调度都发生了啥?

Go 的网络和锁会不会阻塞线程?

什么时候会阻塞线程?

Go 的对象在内存中是怎样的?

Go 的内存分配是怎样的?

栈的内存是怎么分配的?

GC 是怎样的?

GC 怎么帮我们回收对象?

Go 的 GC 会不会漏掉对象或者回收还在用的对象?

Go GC 什么时候开始?

Go GC 啥时候结束?

Go GC 会不会太慢, 跟不上内存分配的速度?

Go GC 会不会暂停我们的应用? 暂停多久? 影不影响我的请求?

带着这些问题,我们来一起研究 golang 的 runtime

  • Golang Runtime 简介

Golang Runtime 是 go 语言运行所需要的基础设施

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

1.png

  1. 与 Java, Python 不同, Go 并没有虚拟机的概念, Runtime 也直接被编译

成 native code.

  1. Go 的 Runtime 与用户代码一起打包在一个可执行文件中
  2. 用户代码与 Runtime 代码在执行的时候并没有明显的界限, 都是函数调用
  3. go 对系统调用的指令进行了封装, 可不依赖于 glibc
  4. 一些 go 的关键字被编译器编译成 runtime 包下的函数.
  • Runtime 发展历程

2.png

注: GC STW 时间与堆大小, 机器性能, 应用分配偏好, 对象数量均有关. 较早的版本来自网络上的数据. 1.4-1.9 数据来源于 twitter 工程师. 这里是以较大的堆测试, 数据仅供参考. 普通应用的情况好于上述的数值.

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

3.png4.png

4.png

  • GM 模型

一开始, 实现一个简单一点的, 一个全局队列放待运行的 g.新生成 G, 阻塞的 G 变为待运行, M 寻找可运行的 G 等操作都在全局队列中操作, 需要加线程级别的锁。

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

5.png

是不是可以给运行的 M 加个本地队列?

是不是可以剥夺阻塞的 M 的 mcache 给其他 M 使用?

  • GPM 模型

Golang 1.1 中调度为 GPM 模型. 通过引入逻辑 Processer P 来解决 GM 模型的几个问题.

6.png

7.png

  1. mcache 从 M 中移到 P 中.
  2. 不再是单独的全局 runq. 每个 P 拥有自己的 runq. 新的 g 放入自己的 runq. 满了后再批量放入全局 runq 中. 优先从自己的 runq 获取 g 执行
  3. 实现 work stealing, 当某个 P 的 runq 中没有可运行 G 时, 可以从全局获取, 从其他 P 获取
  4. 当 G 因为网络或者锁切换, 那么 G 和 M 分离, M 通过调度执行新的 G
  5. 当 M 因为系统调用阻塞或 cgo 运行一段时间后, sysmon 协程会将 P 与 M 分离. 由其他的 M 来结合 P 进行调度
  • G 状态流转

11.png

9.png

10.png

  • 调度

golang 调度的职责就是为需要执行的 Go 代码(G)寻找执行者(M)以及执行的准许和资源(P).

并没有一个调度器的实体, 调度是需要发生调度时由 m 执行 runtime.schedule 方法进行的.

调度时机:

  1. channel, mutex 等 sync 操作发生了协程阻塞
  2. time.sleep
  3. 网络操作暂时未 ready
  4. gc
  5. 主动 yield
  6. 运行过久或系统调用过久
  7. 等等

调度流程:

实际调度代码复杂很多.

如果有分配到 gc mark 的工作需要做 gc mark.

local runq 有就运行 local 的,

没有再看全局的 runq 是否有,

再看能否从 net 中 poll 出来,

从其他 P steal 一部分过来.

....

实在没有就 stopm

12.png

  • sysmon 协程

P 的数量影响了同时运行 go 代码的协程数. 如果 P 被占用很久, 就会影响调度.sysmon 协程的一个功能就是进行抢占.

sysmon 协程是在 go runtime 初始化之后, 执行用户编写的代码之前, 由 runtime 启动的不与任何 P 绑定, 直接由一个 M 执行的协程. 类似于 linux 中的执行一些系统任务的内核线程.

可认为是 10ms 执行一次. (初始运行间隔为 20us, sysmon 运行 1ms 后逐渐翻倍, 最终每 10ms 运行一次. 如果有发生过抢占成功, 则又恢复成初始 20us 的运行间隔, 如此循环)

13.png

  1. 每 sysmon tick 进行一次 netpoll(在 STW 结束,和 M 执行查找可运行的 G 时也会执行 netpoll)获取 fd 事件, 将与之相关的 G 放入全局 runqueue
  2. 每次 sysmon 运行都执行一次抢占, 如果某个 P 的 G 执行超过 1 个 sysmon tick, 则执行抢占. 正在执行系统调用的话, 将 P 与 M 脱离(handoffp); 正在执行 Go 代码,则通知抢占(preemptone).
  3. 每 2 分钟如果没有执行过 GC, 则通知 gchelper 协程执行一次 GC
  4. 如果开启 schdule trace 的 debug 信息(例如 GODEBUG=schedtrace=5000,scheddetail=1), 则

按照给定的间隔打印调度信息

每 5 分钟归还 GC 后不再使用的 span 给操作系统(scavenge)

  • 协作式抢占

retake()调用 preemptone()将被抢占的 G 的 stackguard0 设为 stackPreempt,

被设置抢占标记的 G 进行下一次函数调用时, 检查栈空间失败. 进而触发 morestack()(汇编代码,位于

asm_XXX.s 中)然后进行一连串的函数调用,主要的调用过程如下:

morestack()(汇编代码)-> newstack() -> gopreempt_m() -> goschedImpl() -> schedule()

  • 网络

JavaScript 网络操作是异步非阻塞的, 通过事件循环, 回调对应的函数.一些状态机模式的框架, 每次网络操作都有一个新的状态.

代码执行流被打散.

用户态的协程: 结合 epoll, nonblock 模式的 fd 操作;

网络操作未 ready 时的切换协程和 ready 后把相关协程添加到待运行队列. 网络操作达到既不阻塞线程, 又是同步执行流的效果.

14.png

  1. 封装 epoll, 有网络操作时会 epollcreate 一个 epfd.
  2. 所有网络 fd 均通过 fcntl 设置为 NONBLOCK 模式, 以边缘触发模式放入 epoll 节点中.
  3. 对网络 fd 执行 Accept(syscall.accept4),Read(syscall.read), Write(syscall.write)操作时, 相关

操作未 ready, 则系统调用会立即返回 EAGAIN; 使用 gopark 切换该协程

  1. 在不同的时机, 通过 epollwait 来获取 ready 的 epollevents, 通过其中 data 指针可获取对应的 g, 将其

置为待运行状态, 添加到 runq

  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    498 引用 • 1395 回帖 • 249 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • SOHO

    为成为自由职业者在家办公而努力吧!

    7 引用 • 55 回帖
  • Elasticsearch

    Elasticsearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful 接口。Elasticsearch 是用 Java 开发的,并作为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

    117 引用 • 99 回帖 • 210 关注
  • WebComponents

    Web Components 是 W3C 定义的标准,它给了前端开发者扩展浏览器标签的能力,可以方便地定制可复用组件,更好的进行模块化开发,解放了前端开发者的生产力。

    1 引用 • 9 关注
  • JRebel

    JRebel 是一款 Java 虚拟机插件,它使得 Java 程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响。

    26 引用 • 78 回帖 • 679 关注
  • Mobi.css

    Mobi.css is a lightweight, flexible CSS framework that focus on mobile.

    1 引用 • 6 回帖 • 757 关注
  • Hadoop

    Hadoop 是由 Apache 基金会所开发的一个分布式系统基础架构。用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。

    89 引用 • 122 回帖 • 616 关注
  • 服务器

    服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。

    125 引用 • 585 回帖
  • Bootstrap

    Bootstrap 是 Twitter 推出的一个用于前端开发的开源工具包。它由 Twitter 的设计师 Mark Otto 和 Jacob Thornton 合作开发,是一个 CSS / HTML 框架。

    18 引用 • 33 回帖 • 645 关注
  • HBase

    HBase 是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的 Google 论文 “Bigtable:一个结构化数据的分布式存储系统”。就像 Bigtable 利用了 Google 文件系统所提供的分布式数据存储一样,HBase 在 Hadoop 之上提供了类似于 Bigtable 的能力。

    17 引用 • 6 回帖 • 60 关注
  • 京东

    京东是中国最大的自营式电商企业,2015 年第一季度在中国自营式 B2C 电商市场的占有率为 56.3%。2014 年 5 月,京东在美国纳斯达克证券交易所正式挂牌上市(股票代码:JD),是中国第一个成功赴美上市的大型综合型电商平台,与腾讯、百度等中国互联网巨头共同跻身全球前十大互联网公司排行榜。

    14 引用 • 102 回帖 • 317 关注
  • Laravel

    Laravel 是一套简洁、优雅的 PHP Web 开发框架。它采用 MVC 设计,是一款崇尚开发效率的全栈框架。

    20 引用 • 23 回帖 • 741 关注
  • Sillot

    Insights(注意当前设置 master 为默认分支)

    汐洛彖夲肜矩阵(Sillot T☳Converbenk Matrix),致力于服务智慧新彖乄,具有彖乄驱动、极致优雅、开发者友好的特点。其中汐洛绞架(Sillot-Gibbet)基于自思源笔记(siyuan-note),前身是思源笔记汐洛版(更早是思源笔记汐洛分支),是智慧新录乄终端(多端融合,移动端优先)。

    主仓库地址:Hi-Windom/Sillot

    文档地址:sillot.db.sc.cn

    注意事项:

    1. ⚠️ 汐洛仍在早期开发阶段,尚不稳定
    2. ⚠️ 汐洛并非面向普通用户设计,使用前请了解风险
    3. ⚠️ 汐洛绞架基于思源笔记,开发者尽最大努力与思源笔记保持兼容,但无法实现 100% 兼容
    29 引用 • 25 回帖 • 112 关注
  • Kotlin

    Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,由 JetBrains 设计开发并开源。Kotlin 可以编译成 Java 字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。在 Google I/O 2017 中,Google 宣布 Kotlin 成为 Android 官方开发语言。

    19 引用 • 33 回帖 • 82 关注
  • Outlook
    1 引用 • 5 回帖
  • Caddy

    Caddy 是一款默认自动启用 HTTPS 的 HTTP/2 Web 服务器。

    12 引用 • 54 回帖 • 169 关注
  • abitmean

    有点意思就行了

    37 关注
  • SMTP

    SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。

    4 引用 • 18 回帖 • 635 关注
  • Wide

    Wide 是一款基于 Web 的 Go 语言 IDE。通过浏览器就可以进行 Go 开发,并有代码自动完成、查看表达式、编译反馈、Lint、实时结果输出等功能。

    欢迎访问我们运维的实例: https://wide.b3log.org

    30 引用 • 218 回帖 • 643 关注
  • 旅游

    希望你我能在旅途中找到人生的下一站。

    95 引用 • 901 回帖
  • GitHub

    GitHub 于 2008 年上线,目前,除了 Git 代码仓库托管及基本的 Web 管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。正因为这些功能所提供的便利,又经过长期的积累,GitHub 的用户活跃度很高,在开源世界里享有深远的声望,并形成了社交化编程文化(Social Coding)。

    210 引用 • 2040 回帖
  • NGINX

    NGINX 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 NGINX 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。

    315 引用 • 547 回帖 • 1 关注
  • 前端

    前端技术一般分为前端设计和前端开发,前端设计可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括 HTML、CSS 以及 JavaScript 等。

    245 引用 • 1338 回帖
  • Shell

    Shell 脚本与 Windows/Dos 下的批处理相似,也就是用各类命令预先放入到一个文件中,方便一次性执行的一个程序文件,主要是方便管理员进行设置或者管理用的。但是它比 Windows 下的批处理更强大,比用其他编程程序编辑的程序效率更高,因为它使用了 Linux/Unix 下的命令。

    124 引用 • 74 回帖 • 1 关注
  • 外包

    有空闲时间是接外包好呢还是学习好呢?

    26 引用 • 233 回帖 • 1 关注
  • Quicker

    Quicker 您的指尖工具箱!操作更少,收获更多!

    37 引用 • 156 回帖
  • Solidity

    Solidity 是一种智能合约高级语言,运行在 [以太坊] 虚拟机(EVM)之上。它的语法接近于 JavaScript,是一种面向对象的语言。

    3 引用 • 18 回帖 • 437 关注
  • FlowUs

    FlowUs.息流 个人及团队的新一代生产力工具。

    让复杂的信息管理更轻松、自由、充满创意。

    1 引用 • 1 关注