Golang优化-优雅退出

通过信号量可以监听到系统发出的关停,程序意外关闭、退出、重启等,可以记录相关信息和退出前的操作。

系统支持的信号类型,其中 1~31项为不可靠信号, 34~64为可靠信号

1.SIGHUP:当终端断开时,将发送该信号给终端控制进程。SIGHUP信号还可用于守护进程。
2.SIGINT:当用户键入终端中断字符(如:Ctrl + C)终端驱动程序将发送该信号给前台进程组。该信号默认行为是终止进程。
3.SIGQUIT:当用户在键盘键入退出字符(如Ctrl+\)时,该信号将发往前台进程组。默认情况下,该信号终止进程,并生成可用于调试的核心转储文件。当进程陷入无限循环或者不在响应,使用该信号很合适。
4.SIGILL:进程试图非法执行机器语言指令,系统将向该进程发送该信号。
5.SIGTRAP:该信号用来实现断点调试功能以及strace命令所执行的跟踪系统调用功能。
6.SIGABRT:当进程调用abort函数时,系统向该进程发送该信号。默认情况下,该信号会终止进程,并产生核心转储文件。
7.SIGBUS:总线错误,表示发生了某种内存访问错误。当使用mmap()所创建的内存映射时,如果试图访问的地址超出了底层内存映射文件的结尾,会产生该错误。
8.SIGFPE:在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。
9.SIGKILL:此信号为必杀信号,处理器程序无法阻塞、忽略或者捕获,故而总能杀死进程(僵尸进程除外)。
10.SIGUSR1:用户自定义信号,内核绝不会为进程产生该信号。
11.SIGSEGV:试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.
12.SIGUSR2:同SIGUSR1描述。
13.SIGPIPE:当某一进程试图向管道,FIFO或套接字写入信息时,如果这些设备并无相应的阅读进程,系统将产生该信号。(管道破裂)
14.SIGALRM:经调用alarm()或setitimer()而设置的实时定时器一旦到期,内核将产生该信号。
15.SIGTERM:这是用来终止进程的标准信号,也是kill和killall命令所发送的默认信号。用户经常会使用kil -9显示向进程发送SIGKILL信号,然而这一做法通常是错误的。精心设计的应用程序应当为SIGTERM信号设置处理器程序,以便于其能够预先清理临时文件和释放资源,做到全身而退。发送SIGKILL信号可以杀掉某个进程,从而绕开了SIGTERM的信号处理程序。因此,总是应该首先尝试使用SIGTERM信号来终止进程,而把SIGKILL信号作为最后手段,去对付那些失控的进程。
16.SIGSTKFLT:linux对该信号做了定义,但并未加以使用。
17.SIGCHLD:当父进程的某一子进程退出时,内核将向父进程发送该信号。
18.SIGCONT:该信号发送给已停止的进程,进程将恢复运行。当接收信号的进程当前处于非停止状态 时,默认情况下将忽略该信号。
19.SIFSTOP:进程收到该信号将停止运行,处理器程序无法将其阻塞、忽略或者捕获,故而总能停止进程。
20.SIGTSTP:作业控制的停止信号,当用户在键盘输入挂起字符(如:Ctrl+Z)时,将发送该信号给前台进程组,使其停止运行。(该信号可以被处理和忽略)
21.SIGTTIN:在作业控制shell下运行时,若后台进程组试图对终端进行read()操作,终端驱动程序则将该进程组发送该信号。该信号默认将停止进程。
22.SIGTTOU:该信号与SIGTTIN类似,但在写终端(或修改终端模式)时收到。
23.SIGURG:系统发送该信号给一个进程,表示套接字上存在带外(紧急)数据。
24.SIGXCPU:当进程的CPU时间超出对应的资源限制时,将发送此信号给进程。
25.SIGXFSZ:如果进程试图增大文件而突破对进程文件大小的资源限制时,将发送该信号给进程。
26.SIGVTALRM:虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间。
27.SIGPROF:类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.
28.SIGWINCH:窗口大小改变时发出该信号。
29.SIGIO:文件描述符准备就绪, 可以开始进行输入/输出操作。
30.SIGPWR:电源故障信号。
31.SIGSYS:如果进程发起的系统调用有误,将产生该信号。

监听SIGINT和SIGKILL信号

ctrl+c 产生了一个 SIGINT(中断信号)。
kill 9发送一个 SIGKILL信号 终止进程的信号来结束进程。

func main() {
    fmt.Println("start")
    go func() {
        for {
            time.Sleep(time.Second)
            fmt.Println(time.Now().Unix())
        }
    }()
    quit := make(chan os.Signal)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    fmt.Println("处理关闭逻辑")
    //......
    fmt.Println("关闭成功")
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 1

http server 的启动和关闭 ,以及 linux signal 信号的注册和处理,基于 errgroup 实现的,保证能够一个退出,全部注销退出。

func main() {
    group, ctx := errgroup.WithContext(context.Background())

    mux := http.NewServeMux()
    mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello Go")
    })

    server := http.Server{
        Handler: mux,
        Addr:    ":8889",
    }

    // 利用无缓冲chan 模拟单个服务错误退出
    serverOut := make(chan struct{})
    mux.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) {
        serverOut <- struct{}{} // 阻塞
    })

    // -- 测试 http server 的启动和退出 --

    // g1 启动http server服务
    // g1 退出后, context 将不再阻塞,g2, g3 都会随之退出
    group.Go(func() error {
        return server.ListenAndServe()
    })

    // g2
    // g2 退出时,调用了 shutdown,g1 也会退出
    group.Go(func() error {
        select {
        case <-serverOut:
            fmt.Println("server closed") // 退出会触发 g.cancel, ctx.done 会收到信号
        case <-ctx.Done():
            fmt.Println("errgroup exit")
        }

        timeoutCtx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
        defer cancel()
        log.Println("shutting down server...")
        return server.Shutdown(timeoutCtx)
    })

    // g3 linux signal 信号的注册和处理
    // g3 捕获到 os 退出信号将会退出
    // g3 退出后, context 将不再阻塞,g2 会随之退出
    // g2 退出时,调用了 shutdown,g1 会退出
    group.Go(func() error {
        quit := make(chan os.Signal, 1)
        // sigint 用户ctrl+c, sigterm程序退出
        signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

        select {
        case <-ctx.Done():
            return ctx.Err()
        case sig := <-quit:
            return errors.Errorf("get os exit: %v", sig)
        }
    })

    // 然后 main 函数中的 g.Wait() 退出,所有协程都会退出
    err := group.Wait()
    fmt.Println(err)
    fmt.Println(ctx.Err())
}
2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!