课程名称:深入Go底层原理,重写Redis中间件实战
课程章节:5-7,5-8
课程讲师:Moody
课程内容:
协程虽然可以被gopark和系统调用完成时这样的操作挂起,但是如果没有这两个操作,协程依然无法被挂起。这就需要一个机制来主动挂起协程。
通过go build -gcflags -S main.go 来看,系统在进行方法调用的时候,执行了runtime.morestack方法。
morestack方法本意是用来在方法调用的时候检查栈是否有足够的空间。
go在这个经常被调用的方法里面做了钩子,系统监控到goroutine运行超过10毫秒,就认为这个是个大协程,因此就会进行标记抢占。
※ 标记抢占
系统监控协程运行超过10ms
将g.stackguard0 设置为0xfffffade (抢占标志)
执行morestack()时判断是否已经被标记抢占
如果被抢占,直接回到schedule()
※ 基于信号抢占
如果方法并没有涉及到函数调用,那么就永远不会去检查morestack,那么也不会去检查标记位置,最后实现调度。
那么就可以实现一个基于信号的抢占。
操作系统底层中有很多基于信号的通信方式
线程可以注册对应信号的处理函数
注册信号SIGURG 紧急信号的处理函数,该信号用的地方非常少,基本不会发生冲突
GC工作的时候,向目标线程发送信号,因为GC的时候很多工作都停了,适合做抢占
线程收到信号的时候会触发调度
doSigPreempt方法会执行一个汇编方法,最终依然回调到mcall。
该流程主要是在GC里面下钩子,GC每过一段时间必然会进行垃圾回收,正是这个时刻,向线程发起调度信号,从而完成协程调度
课程收获:
这节课基本解释了协程的被动调用方式,明白了这个原理后,就知道为什么协程会比线程快很多,因为协程本身在切换的时候基本不耗费硬件的资源,完全是依赖线程本身的一些功能进行调度