第 64 期 2019-10-24 深入浅出 Golang Runtime

观看视频

深入浅出Golang Runtime 文字版本

分享者

郝以奋@腾讯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 级别。

线程切换过程:

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

协程切换不需要进入和退出系统调用, 在进行上下文切换时也更轻量, 只需要切换几个寄存器, 协程 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 中。

2 个赞