本文记录了本人对Golang调度器的理解和跟踪调度器的方法,特别是一个容易忽略的goroutine执行顺序问题,看了不少篇Golang调度器的文章都没提到这个点,分享出来一块儿学习,欢迎交流指正。git
为了方便刚接触操做系统和高级语言的同窗,先用大白话介绍下什么是调度器。 调度,是将多个程序合理的安排到有限的CPU上来使得每一个程序都可以得以执行,实现宏观的并发执行。好比咱们的电脑CPU只有四核甚至双核,但是咱们却能够在电脑上同时运行几十个程序,这就是操做系统调度器的功劳。但操做系统调度的是进程和线程,线程简单地说就是轻量级的进程,可是每一个线程仍须要MB级别的内存,并且若是两个切换的线程在不一样的进程中,还须要进程切换,会使CPU在调度这件事上花费大量时间。 为了更合理的利用CPU,Golang经过goroutine原生支持高并发,goroutine是由go调度器在语言层面进行调度,将goroutine安排到线程上,能够更充分地利用CPU。github
Golang的调度器在runtime中实现,咱们每一个运行的程序执行前都会运行一个runtime负责调度goroutine,咱们写的代码入口要在main包下的main函数中也是由于runtime.main函数会调用main.main。Golang的调度器在2012被重写过一次,如今使用的是新版的G-P-M调度器,可是咱们仍是先来看下老的G-M调度器,这样才能够更好的体会当前调度器的强大之处。golang
下面是旧调度器的G-P模型: shell
后来Go语言开发者改善了调度器为G-P-M模型,以下图: markdown
是否是感受本身对Go调度器工做原理已经有个初步的了解了?下面指出一个坑给你踩一下,当心了! 请看下面这段代码输出什么:并发
func main() { done := make(chan bool) values := []string{"a", "b", "c"} for _, v := range values { fmt.Println("--->", v) go func(u string) { fmt.Println(u) done <- true }(v) } // wait for all goroutines to complete before exiting for _ = range values { <-done } } 复制代码
先仔细想一下再看答案哦!函数
实际的数据结果是:高并发
---> a
---> b
---> c
c
b
a
复制代码
Go调度器示例代码能够在跟着示例代码学golang中查看,持续更新中,想系统学习Golang的同窗能够关注一下。oop
可能你的第一反应是“不该该是输出a,b,c,吗?为何输出是c,a,b呢?” 这里咱们虽然是使用for循环建立了3个goroutine,并且建立顺序是a,b,c,按以前的分析应该是将a,b,c三个goroutine依次放进P的局部队列,而后按照顺序依次执行a,b,c所在的goroutine,为何每次都是先执行c所在的goroutine呢?这是由于同一逻辑处理器中三个任务被建立后 理论上会按顺序 被放在同一个任务队列,但实际上最后那个任务会被放在专注的next(下一个要被执行的任务的意思)的位置,因此优先级最高,最可能先被执行,因此表现为在同一个goroutine中建立的多个任务中最后建立那个任务最可能先被执行。学习
这段解释来自参考文章《Goroutine执行顺序讨论》中。
GODEBUG这个Go运行时环境变量非常强大,经过给其传入不一样的key1=value1,key2=value2… 组合,Go的runtime会输出不一样的调试信息,好比在这里咱们给GODEBUG传入了”schedtrace=1000″,其含义就是每1000ms,打印输出一次goroutine scheduler的状态。 下面演示使用Golang强大的GODEBUG环境变量能够查看当前程序中Go调度器的状态:
环境为Windows10的Linux子系统(WSL),WSL搭建和使用的代码在learn-golang项目有整理,代码在文末参考的鸟窝的文章中也能够找到。
func main() { var wg sync.WaitGroup wg.Add(10) for i := 0; i < 10; i++ { go work(&wg) } wg.Wait() // Wait to see the global run queue deplete. time.Sleep(3 * time.Second) } func work(wg *sync.WaitGroup) { time.Sleep(time.Second) var counter int for i := 0; i < 1e10; i++ { counter++ } wg.Done() } 复制代码
编译指令:
go build 01_GODEBUG-schedtrace.go
GODEBUG=schedtrace=1000 ./01_GODEBUG-schedtrace
复制代码
结果:
SCHED 0ms: gomaxprocs=4 idleprocs=1 threads=5 spinningthreads=1 idlethreads=0 runqueue=0 [4 0 4 0]
SCHED 1000ms: gomaxprocs=4 idleprocs=4 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0]
SCHED 2007ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 6]
SCHED 3025ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 6]
SCHED 4033ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 6]
SCHED 5048ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 6]
SCHED 6079ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 6]
SCHED 7081ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 6]
SCHED 8092ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 6]
SCHED 9113ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 1 0 1]
SCHED 10129ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 1 0 1]
SCHED 11134ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 1 0 1]
SCHED 12157ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 1 0 1]
SCHED 13170ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 1 0 1]
SCHED 14183ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 1 0 1]
SCHED 15187ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 1 0 1]
SCHED 16187ms: gomaxprocs=4 idleprocs=2 threads=8 spinningthreads=0 idlethreads=5 runqueue=0 [0 0 0 0]
SCHED 17190ms: gomaxprocs=4 idleprocs=2 threads=8 spinningthreads=0 idlethreads=5 runqueue=0 [0 0 0 0]
SCHED 18193ms: gomaxprocs=4 idleprocs=2 threads=8 spinningthreads=0 idlethreads=5 runqueue=0 [0 0 0 0]
SCHED 19196ms: gomaxprocs=4 idleprocs=2 threads=8 spinningthreads=0 idlethreads=5 runqueue=0 [0 0 0 0]
SCHED 20200ms: gomaxprocs=4 idleprocs=4 threads=8 spinningthreads=0 idlethreads=6 runqueue=0 [0 0 0 0]
SCHED 21210ms: gomaxprocs=4 idleprocs=4 threads=8 spinningthreads=0 idlethreads=6 runqueue=0 [0 0 0 0]
SCHED 22219ms: gomaxprocs=4 idleprocs=4 threads=8 spinningthreads=0 idlethreads=6 runqueue=0 [0 0 0 0]
复制代码
看到怎么多输出不要慌, 了解每一个字段的含义就很清晰了:
上面的输出信息已经足够咱们了解咱们的程序运行情况,要想看每一个goroutine、m和p的详细调度信息,能够在GODEBUG时加入,scheddetail
:
GODEBUG=schedtrace=1000,scheddetail=1 ./01_GODEBUG-schedtrace
复制代码
结果以下:
SCHED 0ms: gomaxprocs=4 idleprocs=4 threads=7 spinningthreads=0 idlethreads=2 runqueue=0 gcwaiting=0 nmidlelocked=0 stopwait=0 sysmonwait=0
P0: status=0 schedtick=7 syscalltick=1 m=-1 runqsize=0 gfreecnt=0
P1: status=0 schedtick=2 syscalltick=1 m=-1 runqsize=0 gfreecnt=0
P2: status=0 schedtick=1 syscalltick=1 m=-1 runqsize=0 gfreecnt=0
P3: status=0 schedtick=1 syscalltick=1 m=-1 runqsize=0 gfreecnt=0
M6: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M5: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M4: p=-1 curg=33 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M3: p=-1 curg=49 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M2: p=-1 curg=17 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M1: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 spinning=false blocked=false lockedg=-1
M0: p=-1 curg=14 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
G1: status=4(semacquire) m=-1 lockedm=-1
G2: status=4(force gc (idle)) m=-1 lockedm=-1
G3: status=4(GC sweep wait) m=-1 lockedm=-1
G4: status=4(sleep) m=-1 lockedm=-1
G5: status=4(sleep) m=-1 lockedm=-1
G6: status=4(sleep) m=-1 lockedm=-1
G7: status=4(sleep) m=-1 lockedm=-1
G8: status=4(sleep) m=-1 lockedm=-1
G9: status=4(sleep) m=-1 lockedm=-1
G10: status=4(sleep) m=-1 lockedm=-1
G11: status=4(sleep) m=-1 lockedm=-1
G12: status=4(sleep) m=-1 lockedm=-1
G13: status=4(sleep) m=-1 lockedm=-1
G14: status=3() m=0 lockedm=-1
G33: status=3() m=4 lockedm=-1
G17: status=3() m=2 lockedm=-1
G49: status=3() m=3 lockedm=-1
复制代码
代码能够在跟着示例代码学golang中查看,持续更新中,想系统学习Golang的同窗能够关注一下。
参考资料: