关于Golang调度器一类的话题
首先
我今年开始在工作中使用Golang编写代码,所以研究了一下调度器。查了一下资料,发现不太多,所以我总结了一下。在查看源代码时,我参考了Go 1.9.3版本。为了易理解,我有意地进行了一些粗略的解释,请见谅。如果有错误,请温柔地指正。
每个goroutine的基本讨论
goroutine是绿色线程,也就是不直接使用操作系统的线程。因此,创建goroutine比创建原生线程的操作成本要低得多。当创建多个goroutine时,运行时会自动以多线程的方式执行。详细内容将在后文中讨论。
此外,主程序也被管理为一个goroutine。
日程安排中的角色
重要的角色有M、G和P这三个人。
-
- M(Machine)
OSのスレッドに対応する。
G(Goroutine)
そのまま。
P(Processor)
Gを貯めておくdequeと呼ばれる特殊なキューを持ち、GをMに割りあてる役割を持つ。
GOMAXPROCS環境変数はこれに対応する。
デフォルトで実行環境のコアの数が設定される1
这些是在源代码上使用m、g、p进行定义的。
日程安排的基本机制
这个调度算法被称为work stealing算法。这个work stealing算法对于CPU密集型处理非常高效,但对于会导致IO等待等不必要的阻塞处理不太适用。因此,进行了一些改进,我将在接下来进行说明。
在进入细节之前的准备工作
在详细说明之前,简要介绍必要的先前知识。
-
- グローバルキュー
Pが持つGのキューとは別にグローバルキューというものがある。
通常Gが実行待ち状態になるとPの持つキューに入るが、いくつかの状況ではグローバルキューに入る。
このグローバルキューは、以下の時に取り出される。
Mの自分のキューが空の時に取り出す
Mの自分のキューがまだあったとしても61回に1度取り出す(そうやってグローバルキューがずっと実行されないことを防ぐ)
sysmon
基本的にGOMAXPROCSの数だけのMとPのセットが動いてる、という説明をしてきたが、それとは別にsysmonという関数を実行し続ける特別なMが存在する。これはP無しで実行される。
このsysmonは処理本体は無限ループになっており、そのループの中で後述するnetpollのチェックや、実行時間の長いGのプリエンプト、必要があればGC用のGをグローバルキューに追加、などを行っている。
P idle list
Pは他にやることがなければP idle listに入れられる。
M idle list
Mも他にやることがなければM idle listに入れられる。
当执行syscall时
当syscall结束后,首先会检查是否有空闲的进程,如果有,则将其取出并继续处理。
当网络处理后
网络处理中引入了一个称为netpoller的机制。Go语言库提供的网络处理API在操作上是阻塞的,但是netpoller在运行时将其转化为非阻塞的处理。这种非阻塞的处理实际上利用了操作系统提供的功能,比如在Linux中使用epoll,在BSD中使用kqueue。
在使用epoll的实现中,这些部分使用epoll_ctl进行注册,使用epoll_wait进行取得。
当G的执行时间很长时
如果M持续执行相同的G(至少10毫秒),那么也有能力抢占该G。
当sysmon发现正在执行的G花费超过10毫秒时,将把该G的preempt标志设为true。但在这个时候还不会进行抢占。
实际上抢占的是被标记的一方。当被标记的G进行函数调用时,会检查自己的preempt标志,如果为true,则将其入队到全局队列中。
收到/发送 channel 时
Golang源代码的遍历方法
到此为止,关于运行时的动作谈论结束了。
当阅读Go库的源代码时,有时会遇到声明但没有实现的函数。这可能是由于使用了go:linkname指令或者是在汇编语言中定义的情况。
对于go:linkname指令的情况,可以通过搜索^//go:linkname \S* importpath\.name找到对应的函数。例如,poll.runtime_pollWait就是一个例子,可以通过搜索^//go:linkname \S+ internal/poll\.runtime_pollWait找到相关信息。
$ grep -P -r '^//go:linkname \S+ internal/poll\.runtime_pollWait' .
./runtime/netpoll.go://go:linkname poll_runtime_pollWait internal/poll.runtime_pollWait
./runtime/netpoll.go://go:linkname poll_runtime_pollWaitCanceled internal/poll.runtime_pollWaitCanceled
换句话说,实施就是这个。
- https://github.com/golang/go/blob/go1.9.3/src/runtime/netpoll.go#L164
参考: https://golang.org/cmd/compile/#hdr-编译器指令
如果在汇编语言中定义,可以通过^TEXT.*·Name\进行搜索得到(请注意,在Name之前使用的不是.而是·(U+00B7))。例如,syscall.Syscall就是这样。
$ grep -P -r '^TEXT.*·Syscall\('
syscall/asm_darwin_386.s:TEXT ·Syscall(SB),NOSPLIT,$0-28
syscall/asm_darwin_amd64.s:TEXT ·Syscall(SB),NOSPLIT,$0-56
syscall/asm_darwin_arm.s:TEXT ·Syscall(SB),NOSPLIT,$0-28
syscall/asm_darwin_arm64.s:TEXT ·Syscall(SB),NOSPLIT,$0-56
syscall/asm_freebsd_arm.s:TEXT ·Syscall(SB),NOSPLIT,$0-28
syscall/asm_linux_386.s:TEXT ·Syscall(SB),NOSPLIT,$0-28
syscall/asm_linux_amd64.s:TEXT ·Syscall(SB),NOSPLIT,$0-56
syscall/asm_linux_arm.s:TEXT ·Syscall(SB),NOSPLIT,$0-28
syscall/asm_linux_arm64.s:TEXT ·Syscall(SB),NOSPLIT,$0-56
syscall/asm_linux_mips64x.s:TEXT ·Syscall(SB),NOSPLIT,$0-56
syscall/asm_linux_mipsx.s:TEXT ·Syscall(SB),NOSPLIT,$0-28
syscall/asm_linux_ppc64x.s:TEXT ·Syscall(SB),NOSPLIT,$0-56
syscall/asm_linux_s390x.s:TEXT ·Syscall(SB),NOSPLIT,$0-56
syscall/asm_nacl_386.s:TEXT ·Syscall(SB),NOSPLIT,$12-28
syscall/asm_nacl_amd64p32.s:TEXT ·Syscall(SB),NOSPLIT,$0-28
syscall/asm_nacl_arm.s:TEXT ·Syscall(SB),NOSPLIT,$0-28
syscall/asm_netbsd_arm.s:TEXT ·Syscall(SB),NOSPLIT,$0-28
syscall/asm_openbsd_arm.s:TEXT ·Syscall(SB),NOSPLIT,$0-28
syscall/asm_plan9_386.s:TEXT ·Syscall(SB),NOSPLIT,$0-32
syscall/asm_plan9_amd64.s:TEXT ·Syscall(SB),NOSPLIT,$0-64
syscall/asm_plan9_arm.s:TEXT ·Syscall(SB),NOSPLIT,$0-32
syscall/asm_solaris_amd64.s:TEXT ·Syscall(SB),NOSPLIT,$0
syscall/asm_unix_386.s:TEXT ·Syscall(SB),NOSPLIT,$0-28
syscall/asm_unix_amd64.s:TEXT ·Syscall(SB),NOSPLIT,$0-56
参考:https://golang.org/doc/asm#directives。
另外,在文件名中可以指定操作系统和架构,语法为name_GOOS_GOARCH。
参考链接:https://golang.org/pkg/go/build/#hdr-Build_Constraints
请参考
-
- The Go scheduler – Morsing’s blog
-
- The Go netpoller – Morsing’s blog
-
- GOMAXPROCS | Dave Cheney
-
- Golangのソースコードを研究する(二) goroutineの動作原理 – Qiita
-
- channel – go routine blocking the others one – Stack Overflow
-
- 100万回のWebSocket接続とGo | プログラミング | POSTD
- Golang 垃圾回收剖析 | Legendtkl
主要参考的源代码
https://github.com/golang/go/blob/go1.9.3/src/runtime/proc.go
sysmon, entersyscall, exitsyscall, scheduleらへん
https://github.com/golang/go/blob/go1.9.3/src/runtime/runtime2.go
G, M, Pの定義
https://github.com/golang/go/blob/go1.9.3/src/runtime/chan.go
channel
https://github.com/golang/go/blob/go1.9.3/src/runtime/netpoll.go
https://github.com/golang/go/blob/go1.9.3/src/runtime/netpoll_epoll.go
netpoll
https://github.com/golang/go/blob/master/src/runtime/stack.go
newstack(preemptの話のところ)
从Go1.5开始。在此之前默认值为1。此外,还与逻辑核心数量有关,如果有超线程技术则为两倍。
我希望在阅读源代码时可以更容易地进行搜索。
因此,在这种情况下,即使GOMAXPROCS设置为2,M(与sysmon相结合)也必须是4或更多。
可能还有其他情况,但我遇到了这两种情况。