28

[译] Go 运行时调度器处理系统调用的巧妙方式

 3 years ago
source link: https://mp.weixin.qq.com/s/DxycOYH3pNpJFYdf6ESbhA
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

点击上方蓝色“ Go语言中文网 ”关注, 回复「 电子书 」领全套Go资料

goroutine [1] 是 Go 的一个标志性特点,是被 Go 运行时所管理的轻量线程。Go 运行时使用 一个 M:N 工作窃取调度器 [2] 实现 goroutine,将 Goroutine 复用在操作系统线程上。调度器有着特殊的术语用来描述三个重要的实体;G 是 goroutine,M 是 OS 线程(一个“机器 machine”),P 是“处理器(processor)”,它的核心是有限的资源,而 M 需要这些资源来运行 Go 代码。限制 P 的供应是 Go 用来限制一次执行多少操作以避免整个系统超载的手段。通常来说,每个 OS 所报告的实际的 CPU 有一个对应的 P (P 的数量是 GOMAXPROCS [3] )。

当 Goroutine 执行 网络 IO 或者任何觉得可以异步完成的系统调用操作时,Go 有一个完整的运行时子系统, netpoller [4] ,(使用类似 epoll [5] 的系统调用机制)将看起来像多个单独的同步操作转换为一个单独的等待。goroutine 并没有真正进行阻塞的系统调用,而是像等待一个 channel 就绪那样进入休眠状态等待其网络套接字。如果很难有效地实现,概念上讲这些都是直白的。

无论如何,网络 IO 以及类似的东西远不是 Go 程序可以处理的唯一的系统调用,因此 Go 也必须处理阻塞的系统调用。对 Goroutine 的 M 来说,处理阻塞的系统调用的直接方式是在系统调用前释放 P ,并且在系统调用恢复后尝试重新获取 P 。如果那时候没有空闲的 P ,goroutine 会随着其他等待运行的任务被停放在调度器中。

虽然理论上所有的系统调用都是阻塞的,在实践中不是所有的调用都会阻塞。例如,在现代系统中,获取当前时间的“系统调用”可能甚至没有进入内核(见 Linux 的 vdso(7) [6] )。让 Goroutine 完成释放他们当前的 P 的全部工作再为了这些系统调用重新获取一个 P 有两个问题:首先,所有涉及到的数据结构的锁定(和释放)有着很大的开销。其次,如果可运行的 Goroutine 比 P 多,进行这类系统调用的 Goroutine 无法重新获取 P 并且不得不把自己停放;释放 P 的瞬间,其他 Goroutine 就会被调度到上面。这是额外的运行时开销,有点不公平,并且不利于进行快速系统调度的目的(尤其是那些不进入内核的调用)。

所以 Go 运行时和调度器实际上有两种处理阻塞系统调用的方法,一种悲观方式,应用于预计会很慢的系统调用;另一种乐观方式,应用于预计会很快的系统调用。悲观的系统调用路径实现了直接的方法,运行时在系统调用前主动释放 P,之后尝试将 P 找回来,如果无法获取则停放自身。乐观的系统调用路径不会释放 P,相反,会设置一个特殊的 P 的状态标识并继续进行系统调用。一个特殊的内部 goroutine,sysmon goroutine,定期执行并寻找设置了这个“进行系统调用中”状态的时间太长了的 P,并将 P 从进行系统调用的 Goroutine 那里偷走。当系统调用返回,运行时代码检查它的 P 是否被偷走,如果没有则继续执行(如果 P 被偷走了的话,运行时会尝试获取其他的 P,如果失败可能会停放 goroutine)。

如果一切顺利,乐观的系统调用路径有着非常低的开销(大多数情况下,需要几个 原子比较和交换 [7] 操作)。如果不顺利并且可运行的 Goroutine 的数量比 P 多,一个 P 会有不必要的空闲,通常可能是数十微秒(sysmon Goroutine 最多每 20 微秒运行一次,但如果似乎没有必要的话可以减少运行频率)。可能存在着最坏的情况,但是一般来说,在 Go 运行时方面这是一个值得的抉择。

via: https://utcc.utoronto.ca/~cks/space/blog/programming/GoSchedulerAndSyscalls

作者: ChrisSiebenmann [8] 译者: dust347 [9] 校对: JYSDeveloper [10]

本文由 GCTT [11] 原创编译, Go 中文网 [12] 荣誉推出

参考资料

[1]

goroutine: https://tour.golang.org/concurrency/1

[2]

一个 M:N 工作窃取调度器: https://rakyll.org/scheduler/

[3]

GOMAXPROCS: https://golang.org/pkg/runtime/

[4]

netpoller: https://morsmachine.dk/netpoller

[5]

epoll: https://medium.com/@copyconstruct/the-method-to-epolls-madness-d9d2d6378642

[6]

vdso(7): http://man7.org/linux/man-pages/man7/vdso.7.html

[7]

原子比较和交换: https://en.wikipedia.org/wiki/Compare-and-swap

[8]

ChrisSiebenmann: https://twitter.com/thatcks/

[9]

dust347: https://github.com/dust347

[10]

JYSDeveloper: https://github.com/JYSDeveloper

[11]

GCTT: https://github.com/studygolang/GCTT

[12]

Go 中文网: https://studygolang.com/

推荐阅读

福利

我为大家整理了一份从入门到进阶的 Go 学习资料礼包(下图只是部分),同时还包含学习建议:入门看什么,进阶看什么。

eeMVJrQ.png!mobile

关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「 进群 」,和数万 Gopher 交流学习。

NzmEvyb.jpg!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK