观看视频
分享者
郝以奋@腾讯NOW直播
Go 夜读第 64 期深入浅出 Golang Runtime
内容简介
本次分享将会对 go runtime 的调度,内存分配,gc 做一些细节上的讲解,同时也需要参与者对 runtime 有一些初步了解。
内容大纲
- Golang Runtime 是什么,其发展历程;
- 调度的实质和关键数据结构,函数;
- 内存分配中 mspan, mheap, mcentral, mcache 等数据结构
- Golang GC 发展,Golang 三色标记实现的一些细节,元信息,写屏障,1.5 与 1.12 GC 的区别;
- 一点优化思路与问题排查思路;
- 总结及 question;
- 平时我看 runtime 代码的一些方式;
分享嘉宾
郝以奋,yifhao, 腾讯 NOW 直播后台开发,负责 NOW 直播 CPP+JAVA 双栈 → Golang 转型:框架协同建设,业务功能定制,Go Mod 引入,服务模板,RPC 协议 Go Mod 化,服务模板,Golang 培训,文档等。
目前 NOW 直播后台有 300 多个 Go 服务。
分享信息
时间:2019-10-17 21:00:00 ~ 23:10:00, UTC+8
分享 Slides:GitHub - yifhao/share
回看视频
Q&A 总结
Q: 腾讯现在用go的多吗?
多, 至少 2000 人的级别了,对go的接受度挺高的,使用人数在迅速增加,当然大部分团队还是 cpp。
Q: 腾讯 NOW 直播 go 开发占比多少?
我们都是从其他语言转的,cpp,java->golang,一开始就写 go 的比较少。基本上学习一下,一个星期就可以开始写线上 go 服务了。目前新服务都是 go。
Q: 线程切换的开销
线程切换大概在几微秒级别,协程切换大概在百 ns 级别。
线程切换过程:
- 进入系统调用
- 调度器本身代码执行
- 线程上下文切换: PC, SP 等寄存器,栈,线程相关的一些局部变量,还涉及一些 cache miss 的情况;
- 退出系统调用
协程切换不需要进入和退出系统调用, 在进行上下文切换时也更轻量, 只需要切换几个寄存器, 协程 runtime.g
结构只有 40 多个字段, 而线程的 task struct 有大概 300 个字段.
可参考进程/线程上下文切换会用掉你多少CPU?
协程究竟比线程能省多少开销?
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 的黑魔法
底层数据共享,减少数据拷贝。
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 中。