代码改变世界

golang signal信号处理

2022-04-03 17:02  youxin  阅读(503)  评论(0编辑  收藏  举报

1、使用场景

实际项目中,我们希望修改了配置文件后,但又不想通过重启进程让它重新加载配置文件,可以使用signal的方式进行信号传递,或者我们希望通过信号控制,实现一种优雅的退出方式。Golang为我们提供了signal包,实现信号处理机制,允许Go 程序与传入的信号进行交互。

2、常用的Term信号

3、简单的栗子

 

package main
 
import (
        "fmt"
        "os"
        "os/signal"
)
 
func main() {
        c := make(chan os.Signal)
        signal.Notify(c)
        fmt.Println("start..")
        s := <-c
        fmt.Println("End...", s)
}

1)传递SIGINT信号

[homework@xxxxx signal]$ go run monitor.go
start..
 
 
 
 
#此时,CTL+C发送一个SIGINT信号量,得到输出为:
[homework@xxxxx signal]$ go run monitor.go
start..
^CEnd... interrupt

(2)传递SIGTERM信号

打开2个Term窗口
第一个运行go run monitor.go程序
第二个执行:ps -ef | grep monitor.go | grep grep -v | awk '{print $2}' | xargs kill
#此时,kill命令发送一个SIGTERM信号量,得到输出为:
[homework@xxxxx signal]$ go run monitor.go
start..
Terminated

4、优雅的退出守护进程

(1)何为优雅(graceful)?

Linux Server端的应用程序经常会长时间运行,在运行过程中,可能申请了很多系统资源,也可能保存了很多状态。

在这些场景下,我们希望进程在退出前,可以释放资源或将当前状态dump到磁盘上或打印一些重要的日志,即希望进程优雅退出。

(2)从对优雅退出的理解不难看出:优雅退出可以通过捕获SIGTERM来实现。

A、注册SIGTERM信号的处理函数并在处理函数中做一些进程退出的准备,信号处理函数的注册sigaction()来实现。

B、在主进程的main()中,通过类似于while(!fQuit)的逻辑来检测那个flag变量,一旦fQuit在signal handler function中被置为true,则主进程退出while()循环,接下来就是一些释放资源或dump进程当前状态或记录日志的动作,完成这些后,主进程退出。

栗子:优雅退出go守护进程

 

package main
 
import (
        "fmt"
        "os"
        "os/signal"
        "syscall"
        "time"
)
 
func main() {
        //创建监听退出chan
        c := make(chan os.Signal)
        //监听指定信号 ctrl+c kill
        signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, 
                         syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)
        go func() {
                for s := range c {
                        switch s {
                        case syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT:
                                fmt.Println("Program Exit...", s)
                                GracefullExit()
                        case syscall.SIGUSR1:
                                fmt.Println("usr1 signal", s)
                        case syscall.SIGUSR2:
                                fmt.Println("usr2 signal", s)
                        default:
                                fmt.Println("other signal", s)
                        }
                }
        }()
 
        fmt.Println("Program Start...")
        sum := 0
        for {
                sum++
                fmt.Println("sum:", sum)
                time.Sleep(time.Second)
        }
}
 
func GracefullExit() {
        fmt.Println("Start Exit...")
        fmt.Println("Execute Clean...")
        fmt.Println("End Exit...")
        os.Exit(0)
}

5、信号的订阅

信号的订阅是通过 channel实现的,每个os.Signal channel 都会收听自己相应的事件集。

 

。golang中对信号的处理主要使用os/signal包中的两个方法:一个是notify方法用来监听收到的信号;一个是 stop方法用来取消监听。

 

监听信号

notify方法原型
func Notify(c chan<- os.Signal, sig ...os.Signal)
第一个参数表示接收信号的管道
第二个及后面的参数表示设置要监听的信号,如果不设置表示监听所有的信号。

 

Sometimes we'd like our Go programs to intelligently handle Unix signals. For example, we might want a server to gracefully shutdown when it receives a SIGTERM, or a command-line tool stop processing input if it receives a SIGINT. Here's how to handle signals in Go with channels

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {

    sigs := make(chan os.Signal, 1)
    done := make(chan bool, 1)

    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        sig := <-sigs
        fmt.Println("")
        fmt.Println(sig)
        done <- true
    }()

    fmt.Println("awaiting signal")
    <-done
    fmt.Println("exiting")
}