golang异步协程调度
在1.14的go版本中,官方通过加入信号来进行协程的调度,后续就都支持了这种异步协程抢占,避免了早起的考栈调度时来检查是否执行超时的逻辑。本文简单来对比这种实现的原理。
调度代码
package main
import "fmt"
func main() {
go func() {
for i:=0;i>=0;i++{
fmt.Println(i)
}
}()
for{}
}
1.12版本对比
通过将该段代码放在1.12环境中运行。
GOMAXPROCS=2 GODEBUG='schedtrace=1000,gctrace=1,scheddetail=1' go run main_sch.go > main_log
通过打开调试来观察,并且可以打开一个tail -f main_log来观察协程是否在输出i。
通过观察可以发现在运行一段时间之后,程序会阻塞住,此时观察调度器的输出信息
SCHED 22121ms: gomaxprocs=2 idleprocs=2 threads=16 spinningthreads=0 idlethreads=9 runqueue=0 gcwaiting=0 nmidlelocked=1 stopwait=0 sysmonwait=0
P0: status=0 schedtick=10394 syscalltick=21045 m=-1 runqsize=0 gfreecnt=7
P1: status=0 schedtick=8534 syscalltick=23086 m=-1 runqsize=0 gfreecnt=8
M15: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1
M14: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M13: p=-1 curg=13 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1
M12: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M11: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M10: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M9: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M8: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M7: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=64
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=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M3: p=-1 curg=19 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M2: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 spinning=false blocked=false lockedg=-1
M1: p=-1 curg=17 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=17
M0: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
G1: status=4(semacquire) m=-1 lockedm=-1
G17: status=6() m=1 lockedm=1
G2: status=4(force gc (idle)) m=-1 lockedm=-1
G3: status=4(GC sweep wait) m=-1 lockedm=-1
G18: status=4(finalizer wait) m=-1 lockedm=-1
G19: status=3() m=3 lockedm=-1
G12: status=4(select) m=-1 lockedm=-1
G13: status=3(chan send) m=13 lockedm=-1
G14: status=6() m=-1 lockedm=-1
G43: status=6() m=-1 lockedm=-1
G16: status=6() m=-1 lockedm=-1
G10: status=4(select) m=-1 lockedm=-1
G11: status=4(select) m=-1 lockedm=-1
G51: status=6() m=-1 lockedm=-1
G65: status=4(chan receive) m=-1 lockedm=-1
G4: status=4(GC worker (idle)) m=-1 lockedm=-1
G5: status=4(GC worker (idle)) m=-1 lockedm=-1
G7: status=4(select) m=-1 lockedm=-1
G8: status=4(select) m=-1 lockedm=-1
G45: status=6() m=-1 lockedm=-1
G42: status=6() m=-1 lockedm=-1
G41: status=6() m=-1 lockedm=-1
G6: status=4(select) m=-1 lockedm=-1
G39: status=6() m=-1 lockedm=-1
G9: status=4(select) m=-1 lockedm=-1
G38: status=6() m=-1 lockedm=-1
G59: status=6() m=-1 lockedm=-1
G53: status=6() m=-1 lockedm=-1
G64: status=4(select) m=-1 lockedm=7
G55: status=6() m=-1 lockedm=-1
G61: status=6() m=-1 lockedm=-1
G57: status=6() m=-1 lockedm=-1
G58: status=6() m=-1 lockedm=-1
SCHED 21109ms: gomaxprocs=2 idleprocs=0 threads=5 spinningthreads=0 idlethreads=2 runqueue=0 gcwaiting=1 nmidlelocked=0 stopwait=1 sysmonwait=0
P0: status=3 schedtick=316559 syscalltick=954340 m=2 runqsize=0 gfreecnt=0
P1: status=1 schedtick=3 syscalltick=4 m=0 runqsize=0 gfreecnt=0
M4: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M3: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M2: p=0 curg=18 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=1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1
G1: status=2(chan receive) m=0 lockedm=-1
G2: status=4(force gc (idle)) m=-1 lockedm=-1
G3: status=4(GC sweep wait) m=-1 lockedm=-1
G17: status=4(finalizer wait) m=-1 lockedm=-1
G18: status=2() m=2 lockedm=-1
G4: status=4(GC worker (idle)) m=-1 lockedm=-1
G5: status=4(GC worker (idle)) m=-1 lockedm=-1
SCHED 23123ms: gomaxprocs=2 idleprocs=2 threads=16 spinningthreads=0 idlethreads=9 runqueue=0 gcwaiting=0 nmidlelocked=1 stopwait=0 sysmonwait=0
P0: status=0 schedtick=10394 syscalltick=21045 m=-1 runqsize=0 gfreecnt=7
P1: status=0 schedtick=8534 syscalltick=23086 m=-1 runqsize=0 gfreecnt=8
M15: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1
M14: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M13: p=-1 curg=13 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1
M12: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M11: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M10: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M9: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M8: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M7: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=64
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=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M3: p=-1 curg=19 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M2: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 spinning=false blocked=false lockedg=-1
M1: p=-1 curg=17 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=17
M0: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
G1: status=4(semacquire) m=-1 lockedm=-1
G17: status=6() m=1 lockedm=1
G2: status=4(force gc (idle)) m=-1 lockedm=-1
G3: status=4(GC sweep wait) m=-1 lockedm=-1
G18: status=4(finalizer wait) m=-1 lockedm=-1
G19: status=3() m=3 lockedm=-1
G12: status=4(select) m=-1 lockedm=-1
G13: status=3(chan send) m=13 lockedm=-1
G14: status=6() m=-1 lockedm=-1
G43: status=6() m=-1 lockedm=-1
G16: status=6() m=-1 lockedm=-1
G10: status=4(select) m=-1 lockedm=-1
G11: status=4(select) m=-1 lockedm=-1
G51: status=6() m=-1 lockedm=-1
G65: status=4(chan receive) m=-1 lockedm=-1
G4: status=4(GC worker (idle)) m=-1 lockedm=-1
G5: status=4(GC worker (idle)) m=-1 lockedm=-1
G7: status=4(select) m=-1 lockedm=-1
G8: status=4(select) m=-1 lockedm=-1
G45: status=6() m=-1 lockedm=-1
G42: status=6() m=-1 lockedm=-1
G41: status=6() m=-1 lockedm=-1
G6: status=4(select) m=-1 lockedm=-1
G39: status=6() m=-1 lockedm=-1
G9: status=4(select) m=-1 lockedm=-1
G38: status=6() m=-1 lockedm=-1
G59: status=6() m=-1 lockedm=-1
G53: status=6() m=-1 lockedm=-1
G64: status=4(select) m=-1 lockedm=7
G55: status=6() m=-1 lockedm=-1
G61: status=6() m=-1 lockedm=-1
G57: status=6() m=-1 lockedm=-1
G58: status=6() m=-1 lockedm=-1
SCHED 22117ms: gomaxprocs=2 idleprocs=0 threads=5 spinningthreads=0 idlethreads=2 runqueue=0 gcwaiting=1 nmidlelocked=0 stopwait=1 sysmonwait=0
P0: status=3 schedtick=316559 syscalltick=954340 m=2 runqsize=0 gfreecnt=0
P1: status=1 schedtick=3 syscalltick=4 m=0 runqsize=0 gfreecnt=0
M4: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M3: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M2: p=0 curg=18 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=1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1
G1: status=2(chan receive) m=0 lockedm=-1
G2: status=4(force gc (idle)) m=-1 lockedm=-1
G3: status=4(GC sweep wait) m=-1 lockedm=-1
G17: status=4(finalizer wait) m=-1 lockedm=-1
G18: status=2() m=2 lockedm=-1
G4: status=4(GC worker (idle)) m=-1 lockedm=-1
G5: status=4(GC worker (idle)) m=-1 lockedm=-1
梳理,可以发现G18在绑定到M2运行的时候一直都是运行,M2在被绑定运行的时候就一直运行,并且其他协程都已经停止了,因为此时启动了垃圾回收,强制等待停止所有的运行协程,但是源代码中的main中只有一个空的for循环并且里面无函数调用等栈变更,导致该协程一直无法完成停止,最终程序会堵住。
1.16版本跟踪对比
同理该函数在1.16版本中运行,发现main_log会一直的打印数据并且都不停止。
终端中输出的调试信息如下。
SCHED 25158ms: gomaxprocs=2 idleprocs=2 threads=13 spinningthreads=0 idlethreads=6 runqueue=0 gcwaiting=0 nmidlelocked=1 stopwait=0 sysmonwait=0
P0: status=0 schedtick=1932 syscalltick=6567 m=-1 runqsize=0 gfreecnt=1 timerslen=0
P1: status=0 schedtick=1646 syscalltick=10943 m=-1 runqsize=0 gfreecnt=21 timerslen=0
M12: p=-1 curg=68 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1
M11: p=-1 curg=30 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1
M10: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M9: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M8: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M7: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
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=false lockedg=-1
M4: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M3: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M2: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=2 dying=0 spinning=false blocked=false lockedg=-1
M1: p=-1 curg=17 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=17
M0: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=87
G1: status=4(semacquire) m=-1 lockedm=-1
G17: status=6() 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(GC scavenge wait) m=-1 lockedm=-1
G5: status=4(finalizer wait) m=-1 lockedm=-1
G47: status=6() m=-1 lockedm=-1
G65: status=6() m=-1 lockedm=-1
G8: status=6() m=-1 lockedm=-1
G38: status=6() m=-1 lockedm=-1
G40: status=6() m=-1 lockedm=-1
G36: status=6() m=-1 lockedm=-1
G37: status=6() m=-1 lockedm=-1
G43: status=6() m=-1 lockedm=-1
G41: status=6() m=-1 lockedm=-1
G33: status=4(select) m=-1 lockedm=-1
G35: status=6() m=-1 lockedm=-1
G18: status=4(GC worker (idle)) m=-1 lockedm=-1
G19: status=4(GC worker (idle)) m=-1 lockedm=-1
G29: status=4(select) m=-1 lockedm=-1
G32: status=4(select) m=-1 lockedm=-1
G44: status=6() m=-1 lockedm=-1
G31: status=4(select) m=-1 lockedm=-1
G30: status=3(chan send) m=11 lockedm=-1
G45: status=6() m=-1 lockedm=-1
G51: status=4(select) m=-1 lockedm=-1
G50: status=4(select) m=-1 lockedm=-1
G52: status=4(select) m=-1 lockedm=-1
G53: status=6() m=-1 lockedm=-1
G84: status=6() m=-1 lockedm=-1
G55: status=6() m=-1 lockedm=-1
G68: status=3() m=12 lockedm=-1
G48: status=6() m=-1 lockedm=-1
G87: status=4(select) m=-1 lockedm=0
G59: status=6() m=-1 lockedm=-1
G60: status=6() m=-1 lockedm=-1
G61: status=6() m=-1 lockedm=-1
G62: status=6() m=-1 lockedm=-1
G63: status=6() m=-1 lockedm=-1
G64: status=6() m=-1 lockedm=-1
G88: status=4(chan receive) m=-1 lockedm=-1
gc 9 @24.471s 0%: 0.057+0.20+0.020 ms clock, 0.11+0.10/0.034/0+0.040 ms cpu, 4->4->0 MB, 5 MB goal, 2 P
SCHED 25050ms: gomaxprocs=2 idleprocs=0 threads=5 spinningthreads=1 idlethreads=2 runqueue=1 gcwaiting=0 nmidlelocked=0 stopwait=0 sysmonwait=0
P0: status=1 schedtick=5517 syscalltick=2710834 m=0 runqsize=0 gfreecnt=0 timerslen=1
P1: status=0 schedtick=3750 syscalltick=2012100 m=-1 runqsize=0 gfreecnt=0 timerslen=0
M4: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M3: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=true blocked=true lockedg=-1
M2: p=-1 curg=-1 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=2 dying=0 spinning=false blocked=false lockedg=-1
M0: p=0 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 spinning=false blocked=false lockedg=-1
G1: status=1(preempted) 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
G17: status=4(finalizer wait) m=-1 lockedm=-1
G18: status=1(GC assist marking) m=-1 lockedm=-1
G5: status=4(GC worker (idle)) m=-1 lockedm=-1
G6: status=4(GC worker (idle)) m=-1 lockedm=-1
SCHED 26168ms: gomaxprocs=2 idleprocs=2 threads=13 spinningthreads=0 idlethreads=6 runqueue=0 gcwaiting=0 nmidlelocked=1 stopwait=0 sysmonwait=0
P0: status=0 schedtick=1932 syscalltick=6567 m=-1 runqsize=0 gfreecnt=1 timerslen=0
P1: status=0 schedtick=1646 syscalltick=10943 m=-1 runqsize=0 gfreecnt=21 timerslen=0
M12: p=-1 curg=68 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1
M11: p=-1 curg=30 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1
M10: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M9: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M8: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M7: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
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=false lockedg=-1
M4: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M3: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M2: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=2 dying=0 spinning=false blocked=false lockedg=-1
M1: p=-1 curg=17 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=17
M0: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=87
G1: status=4(semacquire) m=-1 lockedm=-1
G17: status=6() 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(GC scavenge wait) m=-1 lockedm=-1
G5: status=4(finalizer wait) m=-1 lockedm=-1
G47: status=6() m=-1 lockedm=-1
G65: status=6() m=-1 lockedm=-1
G8: status=6() m=-1 lockedm=-1
G38: status=6() m=-1 lockedm=-1
G40: status=6() m=-1 lockedm=-1
G36: status=6() m=-1 lockedm=-1
G37: status=6() m=-1 lockedm=-1
G43: status=6() m=-1 lockedm=-1
G41: status=6() m=-1 lockedm=-1
G33: status=4(select) m=-1 lockedm=-1
G35: status=6() m=-1 lockedm=-1
G18: status=4(GC worker (idle)) m=-1 lockedm=-1
G19: status=4(GC worker (idle)) m=-1 lockedm=-1
G29: status=4(select) m=-1 lockedm=-1
G32: status=4(select) m=-1 lockedm=-1
G44: status=6() m=-1 lockedm=-1
G31: status=4(select) m=-1 lockedm=-1
G30: status=3(chan send) m=11 lockedm=-1
G45: status=6() m=-1 lockedm=-1
G51: status=4(select) m=-1 lockedm=-1
G50: status=4(select) m=-1 lockedm=-1
G52: status=4(select) m=-1 lockedm=-1
G53: status=6() m=-1 lockedm=-1
G84: status=6() m=-1 lockedm=-1
G55: status=6() m=-1 lockedm=-1
G68: status=3() m=12 lockedm=-1
G48: status=6() m=-1 lockedm=-1
G87: status=4(select) m=-1 lockedm=0
G59: status=6() m=-1 lockedm=-1
G60: status=6() m=-1 lockedm=-1
G61: status=6() m=-1 lockedm=-1
G62: status=6() m=-1 lockedm=-1
G63: status=6() m=-1 lockedm=-1
G64: status=6() m=-1 lockedm=-1
G88: status=4(chan receive) m=-1 lockedm=-1
SCHED 26050ms: gomaxprocs=2 idleprocs=0 threads=5 spinningthreads=0 idlethreads=2 runqueue=0 gcwaiting=0 nmidlelocked=0 stopwait=0 sysmonwait=0
P0: status=2 schedtick=5605 syscalltick=2846152 m=-1 runqsize=0 gfreecnt=0 timerslen=0
P1: status=1 schedtick=3846 syscalltick=2090028 m=4 runqsize=0 gfreecnt=0 timerslen=0
M4: p=1 curg=1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1
M3: p=-1 curg=18 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1
M2: p=-1 curg=-1 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=2 dying=0 spinning=false blocked=false lockedg=-1
M0: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
G1: status=2(preempted) m=4 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(GC scavenge wait) m=-1 lockedm=-1
G17: status=4(finalizer wait) m=-1 lockedm=-1
G18: status=3(GC assist marking) m=3 lockedm=-1
G5: status=4(GC worker (idle)) m=-1 lockedm=-1
G6: status=4(GC worker (idle)) m=-1 lockedm=-1
从调度结果上看,无论是等待gc还是正常的协程调度,都不会阻塞程序的运行,这就是基于信号的异步调度方案。
源码分析
基于1.16的代码,我们着重查看一下sysmon的执行过程。
func sysmon() {
lock(&sched.lock)
sched.nmsys++
checkdead()
unlock(&sched.lock)
// For syscall_runtime_doAllThreadsSyscall, sysmon is
// sufficiently up to participate in fixups.
atomic.Store(&sched.sysmonStarting, 0)
lasttrace := int64(0)
idle := 0 // how many cycles in succession we had not wokeup somebody
delay := uint32(0)
for {
if idle == 0 { // start with 20us sleep...
delay = 20
} else if idle > 50 { // start doubling the sleep after 1ms...
delay *= 2
}
if delay > 10*1000 { // up to 10ms
delay = 10 * 1000
}
usleep(delay)
mDoFixup()
// sysmon should not enter deep sleep if schedtrace is enabled so that
// it can print that information at the right time.
//
// It should also not enter deep sleep if there are any active P's so
// that it can retake P's from syscalls, preempt long running G's, and
// poll the network if all P's are busy for long stretches.
//
// It should wakeup from deep sleep if any P's become active either due
// to exiting a syscall or waking up due to a timer expiring so that it
// can resume performing those duties. If it wakes from a syscall it
// resets idle and delay as a bet that since it had retaken a P from a
// syscall before, it may need to do it again shortly after the
// application starts work again. It does not reset idle when waking
// from a timer to avoid adding system load to applications that spend
// most of their time sleeping.
now := nanotime()
if debug.schedtrace <= 0 && (sched.gcwaiting != 0 || atomic.Load(&sched.npidle) == uint32(gomaxprocs)) {
lock(&sched.lock)
if atomic.Load(&sched.gcwaiting) != 0 || atomic.Load(&sched.npidle) == uint32(gomaxprocs) {
syscallWake := false
next, _ := timeSleepUntil()
if next > now {
atomic.Store(&sched.sysmonwait, 1)
unlock(&sched.lock)
// Make wake-up period small enough
// for the sampling to be correct.
sleep := forcegcperiod / 2
if next-now < sleep {
sleep = next - now
}
shouldRelax := sleep >= osRelaxMinNS
if shouldRelax {
osRelax(true)
}
syscallWake = notetsleep(&sched.sysmonnote, sleep)
mDoFixup()
if shouldRelax {
osRelax(false)
}
lock(&sched.lock)
atomic.Store(&sched.sysmonwait, 0)
noteclear(&sched.sysmonnote)
}
if syscallWake {
idle = 0
delay = 20
}
}
unlock(&sched.lock)
}
lock(&sched.sysmonlock)
// Update now in case we blocked on sysmonnote or spent a long time
// blocked on schedlock or sysmonlock above.
now = nanotime()
// trigger libc interceptors if needed
if *cgo_yield != nil {
asmcgocall(*cgo_yield, nil)
}
// poll network if not polled for more than 10ms
lastpoll := int64(atomic.Load64(&sched.lastpoll))
if netpollinited() && lastpoll != 0 && lastpoll+10*1000*1000 < now {
atomic.Cas64(&sched.lastpoll, uint64(lastpoll), uint64(now)) // 检查事件是否就绪
list := netpoll(0) // non-blocking - returns list of goroutines
if !list.empty() {
// Need to decrement number of idle locked M's
// (pretending that one more is running) before injectglist.
// Otherwise it can lead to the following situation:
// injectglist grabs all P's but before it starts M's to run the P's,
// another M returns from syscall, finishes running its G,
// observes that there is no work to do and no other running M's
// and reports deadlock.
incidlelocked(-1)
injectglist(&list)
incidlelocked(1)
}
}
mDoFixup()
if GOOS == "netbsd" {
// netpoll is responsible for waiting for timer
// expiration, so we typically don't have to worry
// about starting an M to service timers. (Note that
// sleep for timeSleepUntil above simply ensures sysmon
// starts running again when that timer expiration may
// cause Go code to run again).
//
// However, netbsd has a kernel bug that sometimes
// misses netpollBreak wake-ups, which can lead to
// unbounded delays servicing timers. If we detect this
// overrun, then startm to get something to handle the
// timer.
//
// See issue 42515 and
// https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=50094.
if next, _ := timeSleepUntil(); next < now {
startm(nil, false)
}
}
if atomic.Load(&scavenge.sysmonWake) != 0 {
// Kick the scavenger awake if someone requested it.
wakeScavenger()
}
// retake P's blocked in syscalls
// and preempt long running G's
if retake(now) != 0 { // 检查阻塞的系统调用和运行时间较长的协程
idle = 0
} else {
idle++
}
// check if we need to force a GC
if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 { // 是否需要强制GC
lock(&forcegc.lock)
forcegc.idle = 0
var list gList
list.push(forcegc.g)
injectglist(&list)
unlock(&forcegc.lock)
}
if debug.schedtrace > 0 && lasttrace+int64(debug.schedtrace)*1000000 <= now {
lasttrace = now
schedtrace(debug.scheddetail > 0) // 这就是本示例打印sched的详细信息
}
unlock(&sched.sysmonlock)
}
}
继续深入retake函数的流程。
// forcePreemptNS is the time slice given to a G before it is
// preempted.
const forcePreemptNS = 10 * 1000 * 1000 // 10ms
func retake(now int64) uint32 {
...
// We can't use a range loop over allp because we may
// temporarily drop the allpLock. Hence, we need to re-fetch
// allp each time around the loop.
for i := 0; i < len(allp); i++ {
_p_ := allp[i]
if _p_ == nil {
// This can happen if procresize has grown
// allp but not yet created new Ps.
continue
}
pd := &_p_.sysmontick
s := _p_.status
sysretake := false
if s == _Prunning || s == _Psyscall {
// Preempt G if it's running for too long.
t := int64(_p_.schedtick)
if int64(pd.schedtick) != t {
pd.schedtick = uint32(t)
pd.schedwhen = now
} else if pd.schedwhen+forcePreemptNS <= now {
preemptone(_p_) // 检查协程是否运行了最长10ms 如果达到了则发送信号强制抢占
// In case of syscall, preemptone() doesn't
// work, because there is no M wired to P.
sysretake = true
}
}
...
}
通过preemptone来进行信号的发送来强制将该g进行调度。
func preemptone(_p_ *p) bool {
mp := _p_.m.ptr()
if mp == nil || mp == getg().m {
return false
}
gp := mp.curg
if gp == nil || gp == mp.g0 {
return false
}
gp.preempt = true
// Every call in a go routine checks for stack overflow by
// comparing the current stack pointer to gp->stackguard0.
// Setting gp->stackguard0 to StackPreempt folds
// preemption into the normal stack overflow check.
gp.stackguard0 = stackPreempt
// Request an async preemption of this P.
if preemptMSupported && debug.asyncpreemptoff == 0 {
_p_.preempt = true
preemptM(mp)
}
return true
}
func preemptM(mp *m) {
// On Darwin, don't try to preempt threads during exec.
// Issue #41702.
if GOOS == "darwin" || GOOS == "ios" {
execLock.rlock()
}
if atomic.Cas(&mp.signalPending, 0, 1) {
if GOOS == "darwin" || GOOS == "ios" {
atomic.Xadd(&pendingPreemptSignals, 1)
}
// If multiple threads are preempting the same M, it may send many
// signals to the same M such that it hardly make progress, causing
// live-lock problem. Apparently this could happen on darwin. See
// issue #37741.
// Only send a signal if there isn't already one pending.
signalM(mp, sigPreempt) // 发送强制信号
}
if GOOS == "darwin" || GOOS == "ios" {
execLock.runlock()
}
}
...
func signalM(mp *m, sig int) {
pthread_kill(pthread(mp.procid), uint32(sig))
}
至此可以清晰的看出通过pthread_kill来想线程发送信号,从而完成线程通知而完成强制调度,此时在go启动的过程中就注册了信号处理函数,在完成一系列安全等的检查之后就会调用schedule()函数。
c语言的实现
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
/* Simple error handling functions */
static void *
sig_thread(void *arg)
{
sigset_t *set = arg;
int s, sig;
for (;;) {
printf("thread(%lu) i: %d\n", pthread_self(), 1);
sleep(60);
printf("next (%lu) i: %d\n", pthread_self(), 1);
}
}
//信号的异步处理方式
void sig_handler(int signo)
{
printf("thread(%lu) call sig_handler: signal(%d)\n", pthread_self(), signo);
}
//信号的同步处理
void sigwait_handler(int signo)
{
printf("sub thread(%lu) call sigwait_handler: signal(%d)\n",pthread_self(), signo);
}
int
main(int argc, char *argv[])
{
int NUMTHREADS = 5;
pthread_t threads[NUMTHREADS];
sigset_t set;
int s;
printf("main thread(%lu) \n",pthread_self());
//注册异步信号处理函数
if(signal(SIGUSR1, sig_handler)== SIG_ERR){
perror("signal sigusr1 error");
}
if(signal(SIGTSTP, sig_handler)==SIG_ERR){
perror("signal sigtstp error");
}
int i = 0;
for(i=0;i<NUMTHREADS;i++) {
pthread_create(&threads[i], NULL, &sig_thread, NULL);
}
for(;;){
int i=0;
for(i=0;i<NUMTHREADS;i++){
pthread_kill(threads[i],SIGUSR1);
sleep(10);
}
}
/* Main thread carries on to create other threads and/or do
other work. */
printf("Hello, World!");
sleep(10000);
}
此时,我们编译运行一下,就能从终端中得到如下信息。
main thread(140400981489472)
thread(140400973096704) i: 1
thread(140400964704000) i: 1
thread(140400939525888) i: 1
thread(140400956311296) i: 1
thread(140400973096704) call sig_handler: signal(10)
next (140400973096704) i: 1
thread(140400973096704) i: 1
thread(140400947918592) i: 1
thread(140400964704000) call sig_handler: signal(10)
next (140400964704000) i: 1
thread(140400964704000) i: 1
thread(140400956311296) call sig_handler: signal(10)
next (140400956311296) i: 1
thread(140400956311296) i: 1
thread(140400947918592) call sig_handler: signal(10)
next (140400947918592) i: 1
thread(140400947918592) i: 1
从输出来看,每个线程都调用了主线程注册的回调函数来进行处理。go的异步协程调度也基于此原理。
总结
本文主要是简单概要的尝试了一下go1.14之后新加的信号异步协程调度方案,从使用的效果上来看,这种方案确实增强了在某些情况下协程长时间运行的问题,这也使得用户写的程序的健壮性会更强。