Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第 64 期深入浅出Golang Runtime-yifhao #492

Closed
yifhao opened this issue Oct 18, 2019 · 12 comments
Closed

第 64 期深入浅出Golang Runtime-yifhao #492

yifhao opened this issue Oct 18, 2019 · 12 comments
Labels
Go 夜读 Go 夜读:主题分享 已分享 ✅ Go 夜读的分享状态:分享已完成。 源码阅读 Go 夜读分享主题:有关 Go 的源码阅读,源码分析

Comments

@yifhao
Copy link

yifhao commented Oct 18, 2019

【Go 夜读】 深入浅出Golang Runtime

本次分享对go runtime的调度, 内存分配, gc做一些细节上的讲解, 需要参与者对runtime有一些初步了解. (在2019.08.17 深圳Gopher Meetup的分享)

大纲

  1. Golang Runtime是什么, 其发展历程
  2. 调度的实质和关键数据结构, 函数
  3. 内存分配中mspan, mheap, mcentral, mcache等数据结构
  4. Golang GC发展, Golang三色标记实现的一些细节, 元信息, 写屏障, 1.5与1.12 GC的区别
  5. 一点优化思路与问题排查思路
  6. 总结及question
  7. 平时我看runtime代码的一些方式(时间够的话)

分享者自我介绍

郝以奋, yifhao, 腾讯NOW直播后台开发, 负责NOW直播 CPP+JAVA双栈 -> Golang转型: 框架协同建设, 业务功能定制, Go Mod引入, 服务模板, RPC协议Go Mod化, 服务模板, Golang培训, 文档等.
目前后台有300多个Go服务.

分享时间

2019-10-24 21:00 UTC+8

分享地址

https://zoom.us/j/6923842137

Slides

https://github.com/Frank-Hust/share

参考资料

备注

针对此次分享的 QA 请分享者在分享之后,整理同步到此 issues 后面。

@Shitaibin
Copy link

mark

@huangxingx
Copy link

期待

@yangwenmai yangwenmai added Go 夜读 Go 夜读:主题分享 已排期 🗓️ Go 夜读的分享状态:已确定分享时间。 源码阅读 Go 夜读分享主题:有关 Go 的源码阅读,源码分析 labels Oct 21, 2019
@yangwenmai yangwenmai changed the title 深入浅出Golang Runtime-yifhao 第 64 期深入浅出Golang Runtime-yifhao Oct 21, 2019
@KScaesar
Copy link

請問GPM的P,上面所謂的資源是什麼意思
看很多次相關文章都不太了解,為什麼一定要G綁定P才可以執行
不懂原因
疑問是G上沒有資源嗎? 不是有很多變數?

@yangwenmai yangwenmai added 已分享 ✅ Go 夜读的分享状态:分享已完成。 and removed 已排期 🗓️ Go 夜读的分享状态:已确定分享时间。 labels Oct 24, 2019
@i-coder-robot
Copy link

希望能夯实一下基础,昨天的分享干货好多,硬核好多~ 我是野生程序猿!

@fengbianyun
Copy link

干货太多,希望大佬多分享一些Runtime相关知识。

@byphper
Copy link

byphper commented Oct 25, 2019

有没有回放视频呢

@yangwenmai
Copy link
Member

有没有回放视频呢

https://www.bilibili.com/video/av73297683

https://youtu.be/oFJL8S1dwsw

可以关注、订阅。

@yifhao
Copy link
Author

yifhao commented Oct 27, 2019

Q: 腾讯现在用go的多吗?

多, 至少 2000 人的级别了,对go的接受度挺高的,使用人数在迅速增加,当然大部分团队还是 cpp。

Q: 腾讯 NOW 直播 go 开发占比多少?

我们都是从其他语言转的,cpp,java->golang,一开始就写 go 的比较少。基本上学习一下,一个星期就可以开始写线上 go 服务了。目前新服务都是 go。

Q: 线程切换的开销

线程切换大概在几微妙级别,协程切换大概在百 ns 级别。

线程切换过程:

  1. 进入系统调用
  2. 调度器本身代码执行
  3. 线程上下文切换: PC, SP 等寄存器,栈,线程相关的一些局部变量,还涉及一些 cache miss 的情况;
  4. 退出系统调用

协程切换不需要进入和退出系统调用, 在进行上下文切换时也更轻量, 只需要切换几个寄存器, 协程 runtime.g 结构只有 40 多个字段, 而线程的 task struct 有大概 300 个字段.

可参考进程/线程上下文切换会用掉你多少CPU?
https://zhuanlan.zhihu.com/p/79772089

协程究竟比线程能省多少开销?
https://zhuanlan.zhihu.com/p/80037638

Q: 为啥是边缘触发, 而不是水平触发的方式?

因为网络操作 ready 和未 ready 对于协程来说就是状态的切换。
socket fd ready 了, 阻塞之上的协程就从 waiting 变成 runnable。
操作时 socket fd 未 ready,那协程就从 running 变成 waiting。
假如采取水平触发,如果一个协程因为某个连接读而变成 waiting 状态,这个连接有数据后,与之关联的协程就变成 ready,这个协程一直没去读数据,那水平触发一直就会 poll 出来该 fd,没必要。

Q: 内存什么时候释放?

内存释放分两步
没有存活对象的 span 被 GC 回收, 归还到 mheap 结构中,变成 free 的 page。
sysmon 协程会扫描,超过一段时间没有再被使用的 page(1.12 机制有改变), 通过 madvise 系统调用告诉操作系统,这些 page 对应的物理内存不再需要了,可以与虚拟内存解绑,给其他分配使用。

Q: 0.1+13+0.3ms 三个时间的意思?

GCDEBUG=gctrace=1 会打印出 gc 相关的时间,这三个分别代表,gc 开始时第一个 stw 的 wall time, 并发标记的 wall time 以及 GC 标记结束阶段 stw 的 wall time。

Q: []byte 于 string 的黑魔法

底层数据共享,减少数据拷贝。
https://jaycechant.info/2019/golang-unsafe-cast-between-string-and-bytes/

Q: 之前说的 netpoll,被 gopark 挂起的 G 扔哪了,怎么找到对应的 G,然后又怎么扔给对应的 M 的 runQ 的?

并没有扔哪里去,也没放在哪个队列。
一个协程因为某个网络 fd 的操作阻塞时,会把该 fd 添加到 epoll 中,使用以下系统调用。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
};

go 在 epoll_event 中的 epoll_data_t 放了一个指针值,该指针指向一个包含 runtime.g 的结构体。
下次 epoll_wait 时,便可把该 epoll_data_t 也 poll 出来,相当于与该 fd 关联的上下文,也就可以找到阻塞其上的协程。

不需要再放回对应的 M 的 runq 中,目前是通过 injectglist 放在全局的 runq 中.
netpoll有比较多的地方在调用, sysmon中定时调用, findrunnable中调度, start the world时(比如gc结束), pollwork是gcDrain时会调用.
image

至于为什么是放到全局runq, 对于sysmon, gc这块比较好解释,因为本来就没和特定P绑定, 放到全局会比较好一些. 而对于findrunnable来说, 有两次netpoll. 第一次在local和global都没有任务时, 去poll, 放到全局应该是为了均衡吧.
image

poll没有再去其他P steal, 如果其他P也没有steal到, 也没有gc任务, 那表明真没任务了, 这时候还会做一次netpoll, 这次一定是为了均衡, 因为其他P都没有, 你这里有, 那应该放到全局, 让其他P去获取.

@hw676018683
Copy link

@Frank-hust 请问一下,在MarkTermination阶段,已经STW了,为啥还需要写屏障呢

@yifhao
Copy link
Author

yifhao commented Oct 29, 2019

@Frank-hust 请问一下,在MarkTermination阶段,已经STW了,为啥还需要写屏障呢

这个问题很复杂, 我也没太搞清楚. 按照目前简略的GC流程来看, 在stw阶段貌似是不需要写屏障的.
我翻了下commit历史, 在1.4-1.5之间这个已经存在了.
应该是因为gc在mark termination阶段, 其实也有对指针进行操作, 所以需要写屏障... 具体哪里有指针操作, 我也不太清楚.
https://go-review.googlesource.com/c/go/+/6990/

Even though the world is stopped the GC may do pointer
writes that need to be protected by write barriers.
This means that the write barrier must be on
continuously from the time the mark phase starts and
the mark termination phase ends. Checks were added to
ensure that no allocation happens during a GC.
// The gcphase is _GCmark, it will transition to _GCmarktermination
// below. The important thing is that the wb remains active until
// all marking is complete. This includes writes made by the GC.

@yifhao
Copy link
Author

yifhao commented Oct 29, 2019

更正一个错误, ppt最后面的一个有趣的问题排查, 有讲到gctrace日志打到磁盘阻塞gc完成, 并不是因为gc没完成而导致其他协程不能运行, 而是后续gc无法开启, 导致实质上的stw.

打印gc trace日志时, 已经start the world了, 其他协程可以开始运行了. 但是在打印gctrace日志时, 还保持着开启gc需要的锁, 所以, 打印gc trace日志一直没完成, 而gc又比较频繁, 比如0.1s一次, 这样会导致下一次gc开始时无法获取锁, 每一个进入gc检查的p阻塞, 实际上就造成了stw.

@yudidi
Copy link

yudidi commented Mar 3, 2020

Q: 调度器在遇到协程阻塞时(4种阻塞),为什么分成2类情况来处理,以及如何处理

image

> 提问者:
如图所示, 这里为什么要分2种情况呢。
1. G 网络和锁切换,为了不浪费M,所以换一个G运行。
2. M 系统调用和cgo,为了不浪费M,所以M,P分离。
Q:不都是因为M中的G进行了某种阻塞操作,为了不浪费M,才考虑换一个G或一个P么?为什么要分出2种情况呢。

> 分享者: 
第2个错了吧,线程因为系统调用或者c的代码阻塞,go是没办法把这个线程恢复的,p影响着系统并行度,所以要把p拿出来。
线程多几个少几个影响不大,p的话,一般就那么几个。

> 提问者: (查找资料后总结)
结合您的反问和附录的2篇资料,我大概梳理了这样一个理解。
前置知识点: go程序中,任何对系统 API 的调用,都会被 runtime 层拦截来方便调度。
go一共有4种阻塞的情况,并且这些阻塞都是可以被runtime检测到的,runtime检测到阻塞时就可以进行优化处理。
1. blocking syscall (for example opening a file) // 系统调用
2. network input // 网络IO
3. channel operations 
4. primitives in the sync package // 锁
4种阻塞可以分为两类:
分类1 (对应情况2,3,4): (只G阻塞,M,P可用的,要利用起来)
1.1 用户代码层面的阻塞(channel,锁), 此时M可以换上其他G继续执行。
1.2 网络阻塞 (netpoller实现G网络阻塞不会导致M被阻塞,仅阻塞G)。
分类2 (对应情况1): (G,M都被阻塞,P可用,要利用起来)
2.1 系统调用(open file)
# 参考
https://www.kancloud.cn/fruitbag/stack_of_gofuny/760985
https://povilasv.me/go-scheduler/

> 分享者: (点评总结,扩展知识)
3,4其实是一种操作, 都是锁.
在go里面网络用的非阻塞模式, 所以不会阻塞线程.

TODO: 如何理解3,4其实都是锁。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Go 夜读 Go 夜读:主题分享 已分享 ✅ Go 夜读的分享状态:分享已完成。 源码阅读 Go 夜读分享主题:有关 Go 的源码阅读,源码分析
Projects
None yet
Development

No branches or pull requests

10 participants