golang runtime 的理解

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

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 发布的第二款编程语言。

    493 引用 • 1385 回帖 • 341 关注

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • GAE

    Google App Engine(GAE)是 Google 管理的数据中心中用于 WEB 应用程序的开发和托管的平台。2008 年 4 月 发布第一个测试版本。目前支持 Python、Java 和 Go 开发部署。全球已有数十万的开发者在其上开发了众多的应用。

    14 引用 • 42 回帖 • 705 关注
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    198 引用 • 120 回帖 • 1 关注
  • TensorFlow

    TensorFlow 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。

    20 引用 • 19 回帖
  • Hexo

    Hexo 是一款快速、简洁且高效的博客框架,使用 Node.js 编写。

    21 引用 • 140 回帖 • 14 关注
  • JetBrains

    JetBrains 是一家捷克的软件开发公司,该公司位于捷克的布拉格,并在俄国的圣彼得堡及美国麻州波士顿都设有办公室,该公司最为人所熟知的产品是 Java 编程语言开发撰写时所用的集成开发环境:IntelliJ IDEA

    18 引用 • 54 回帖
  • Flutter

    Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 Flutter 可以与现有的代码一起工作,它正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。

    39 引用 • 92 回帖
  • PWA

    PWA(Progressive Web App)是 Google 在 2015 年提出、2016 年 6 月开始推广的项目。它结合了一系列现代 Web 技术,在网页应用中实现和原生应用相近的用户体验。

    14 引用 • 69 回帖 • 140 关注
  • 周末

    星期六到星期天晚,实行五天工作制后,指每周的最后两天。再过几年可能就是三天了。

    14 引用 • 297 回帖
  • danl
    89 关注
  • Dubbo

    Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,是 [阿里巴巴] SOA 服务化治理方案的核心框架,每天为 2,000+ 个服务提供 3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。

    60 引用 • 82 回帖 • 613 关注
  • BookxNote

    BookxNote 是一款全新的电子书学习工具,助力您的学习与思考,让您的大脑更高效的记忆。

    笔记整理交给我,一心只读圣贤书。

    1 引用 • 1 回帖 • 1 关注
  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 645 关注
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 400 关注
  • React

    React 是 Facebook 开源的一个用于构建 UI 的 JavaScript 库。

    192 引用 • 291 回帖 • 433 关注
  • 七牛云

    七牛云是国内领先的企业级公有云服务商,致力于打造以数据为核心的场景化 PaaS 服务。围绕富媒体场景,七牛先后推出了对象存储,融合 CDN 加速,数据通用处理,内容反垃圾服务,以及直播云服务等。

    26 引用 • 222 回帖 • 169 关注
  • 机器学习

    机器学习(Machine Learning)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。

    76 引用 • 37 回帖
  • Mac

    Mac 是苹果公司自 1984 年起以“Macintosh”开始开发的个人消费型计算机,如:iMac、Mac mini、Macbook Air、Macbook Pro、Macbook、Mac Pro 等计算机。

    164 引用 • 594 回帖 • 1 关注
  • 互联网

    互联网(Internet),又称网际网络,或音译因特网、英特网。互联网始于 1969 年美国的阿帕网,是网络与网络之间所串连成的庞大网络,这些网络以一组通用的协议相连,形成逻辑上的单一巨大国际网络。

    96 引用 • 330 回帖
  • CAP

    CAP 指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。

    11 引用 • 5 回帖 • 582 关注
  • OpenShift

    红帽提供的 PaaS 云,支持多种编程语言,为开发人员提供了更为灵活的框架、存储选择。

    14 引用 • 20 回帖 • 611 关注
  • DevOps

    DevOps(Development 和 Operations 的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

    45 引用 • 25 回帖
  • Quicker

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

    25 引用 • 83 回帖 • 1 关注
  • Telegram

    Telegram 是一个非盈利性、基于云端的即时消息服务。它提供了支持各大操作系统平台的开源的客户端,也提供了很多强大的 APIs 给开发者创建自己的客户端和机器人。

    5 引用 • 35 回帖 • 1 关注
  • 支付宝

    支付宝是全球领先的独立第三方支付平台,致力于为广大用户提供安全快速的电子支付/网上支付/安全支付/手机支付体验,及转账收款/水电煤缴费/信用卡还款/AA 收款等生活服务应用。

    29 引用 • 347 回帖
  • Mobi.css

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

    1 引用 • 6 回帖 • 708 关注
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    19810 引用 • 75848 回帖 • 1 关注