抢占式调度
抢占式调度
协作调度和抢占式调度
协作式调度依靠被调度方主动放权;抢占式调度依靠调度器强制将被调度方被动中断。
1、同步协作式调度:
1.1、主动用户让权:通过runtime.Gosched
调用主动让出执行机会。Gosched是一种主动放弃执行的手段,用户态代码通过调用此接口来让出执行机会,使其他人也能在秘籍的执行过程中获得调度的机会。
1.2、主动调度弃权:当发生执行栈分段时,检查自身的抢占标记,决定是否继续执行
2、异步抢占式调度:
2.1、被动监控抢占:当G阻塞在M上时(系统调用、channel等),系统监控会将P从M上抢夺并分配给其他的M来执行其他的G,而位于被抢夺P的M的本地调度队列中的G则可能会被偷取到其他的M中。
2.2、被动GC抢占:当需要进行垃圾回收时,为了保证不具备主动抢占处理的函数执行时间过长,导致垃圾回收迟迟不能执行导致的高延迟,而强制停止G并转为执行垃圾回收
Go基于信号的异步抢占的过程
1、M注册一个SIGURG信号的处理函数,sighandler
2、sysmon线程检测到执行时间过长的goroutine、gc stw时,会向相应的M发送SIGURG信号
3、收到信号后内核执行sighandler函数,通过pushCall插入asyncPreempt函数调用
4、回到当前goroutine执行asyncPreempt函数,通过mcall切到g0栈执行gopreempt_m
5、将当前goroutine插入到全局可运行队列,M则继续寻找其他goroutine来运行
6、被抢占的goroutine再次调度过来执行时会继续原来的执行流
linux信号处理与goroutine抢占式调度有关系
一些常用的linux信号
SIGINT ctrl-c
SIGTSTP 进程挂起
SIGQUIT 进程结束
SIGTERM 结束程序
SIGKILL kill -9 pid
SIGURG
go程序接收结束信号
package main
import(
"fmt"
"os"
"os/signal"
"syscall"
)
func main(){
//先创建一个os.Signal channel
sigs := make(chan os.Signal,1)
done := make(chan bool,1)
//然后使用signal.Notify注册要接受的信号,有该信号就会发送到sigs
signal.Notify(sigs,syscall.SIGINT,syscall.SIGTERM)
go func(){
sig := <-sigs
fmt.Println(sig)
done <- true
}()
fmt.Println("awaiting signal")
<-done
fmt.Println("exiting")
}
go程序优雅启停
不优雅启停会导致两个问题:
1、进程重启需要关闭监听的端口,会导致用户访问错误
2、当进程重启时应当将还没完成的进程消耗完再杀死
那么优雅启停的思路是,通过exec.Command
fork一个新的进程,同时继承当前进程打开的文件(输入输出,socket等)
file := netListener.File()
path := "/path/to/executable"
args := []string{"-graceful"}
cmd := exec.Command(path,args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.ExtraFiles = []*os.File{file}
err := cmd.Strart()
if err != nil{
}
子进程初始化和网络程序启动
server := &http.Server{Addr:"0.0.0.0:8888"}
var gracefulChild bool
var l net.Listever
var err error
flag.BoolVar(&gracefulChild,"graceful",false,"listen on fd open 3 (internal use only)")
if gracefulChild{
f := os.NewFile(3,"")
l,err = net.FileListener(f)
}else{
l,err = net.Listen("tcp",server.Addr)
}
父进程停止
if gracefulChild{
parent := sysvall.Getppid()
syscall.Kill(parant,syscall.SIGTERM)
}
server.Serve(l)