脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|shell|

服务器之家 - 脚本之家 - Golang - 拦截信号Golang应用优雅关闭的操作方法

拦截信号Golang应用优雅关闭的操作方法

2023-03-16 13:59梦想画家 Golang

这篇文章主要介绍了拦截信号优雅关闭Golang应用,本文介绍了信号的概念及常用信号,并给出了应用广泛的几个示例,例如优雅地关闭应用服务、在命令行应用中接收终止命令,需要的朋友可以参考下

Golang不是像C语言的系统级编程语言,但仍提供了以下特性帮助开发者与底层操作系统进行交互,如信号(singals),os/singals包实现了这些功能。相对于其他语言处理OS信号采用复杂或冗余的方法,Golang内置OS包提供了易用的方法处理Unix信号,非常直接、简单。事实上,类unix系统中通常处理singal是必要的,本文介绍singal概念,并通过示例说明其应用场景。

从示例开始

假设场景:Golang应用在关闭时打印消息:“Thank you for using Golang.” 首先新建main函数,函数体模拟执行一些业务,直到接收到结束命令。

?
1
2
3
4
5
6
func main() {
   for {
      fmt.Println("Doing Work")
      time.Sleep(1 * time.Second)
   }
}

当运行应用,然后从OS发送执行kill信号(Ctrl + C),可能输出结果类似这样:

?
1
2
3
4
Doing Work
Doing Work
Doing Work
Process finished with exit code 2

现在我们希望拦截kill信号并定义处理逻辑:打印必要的关闭信息。

接收信号

首先创建channe接收来自操作系统的命令,os包提供了signal接口处理基于OS规范信号实现。

?
1
killSignal := make(chan os.Signal, 1)

为了通知killSignal,需使用signal包中提供的Notify函数,第一个参数为Signal类型的channel,下一个参数接收一组发给通道的信号列表。

?
1
Notify(c chan<- os.Signal, sig ...os.Signal)

我们也可以使用syscall包通知特定命令的信号:

?
1
signal.Notify(c chan<- os.Signal, syscall.SIGINT, syscall.SIGTERM)

为了处理信号,我们在main函数中使用killSignal通道等待interrupt信号,一旦接收到来自OS的命令,则打印结束消息并接收应用。现在把处理业务的循环代码移入独立的goroute中,这里定义一个匿名函数:

?
1
2
3
4
5
6
go func() {
   for {
      fmt.Println("Doing Work")
      time.Sleep(1 * time.Second)
   }
}()

因为业务代码运行在独立routine中,main函数实现等待killSignal信号并在结束之前打印消息。

?
1
2
<-killSignal
fmt.Println("Thanks for using Golang!")

完整代码

下面把几个部分组装在一起,完整代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
 
import (
    "fmt"
    "os"
    "os/signal"
    "time"
)
 
func main() {
 
    killSignal := make(chan os.Signal, 1)
    signal.Notify(killSignal, os.Interrupt)
    go func() {
        for {
            fmt.Println("Doing Work")
            time.Sleep(1 * time.Second)
        }
    }()
    <-killSignal
    fmt.Println("Thanks for using Golang!")
}

运行程序,看到一直执行业务循环,直到接收到interrupt信号,打印消息并结束:

Doing Work
Doing Work
Doing Work
Thanks for using Golang!

常见信号

对于命令行程序,当按CTRL+C时,会发送SIGINT信号,SIGINT是与指定特定信号数字相关联的名称。信号实际是可以有程序生成的软件中断,也是一种异步处理事件机制。通常为了安全起见,信号采用名称而不是数值生成。

大多数信号及其具体动作都是由操作系统定义的,并还在操作系统权限范围内执行相应功能。因此能够处理某些事件并不能让用户自由地使用系统,有些信号即使生成了也会被操作系统忽略,而由操作系统决定程序允许处理哪些信号。

举例,SIGKILL 和 SIGSTOP 信号也可以被捕获、阻塞或忽略。因为这些信号对于保持操作系统功能的"健全"至关重要,在极端条件下为内核和root用户提供了停止进程的方法。与SIGKILL相关联的数字是9,Linux提供了kill命令发送SIGTERM信号,终止正在运行的程序。可以使用kill -l命令查看完整的支持信号:

图片

有许多信号可用,但并非所有信号都能被程序处理。像SIGKILL和SIGSTOP这样的信号既不能被程序调用,也不能被程序忽略。原因很简单:它们太重要了,不允许被恶意程序滥用。

系统信号分为同步信号和异步信号。其中同步信号是程序执行中的错误触发的信号,如SIGBUS, SIGFPE, SIGSEGV,在 Golang 程序中,同步信号通常会被转换为 runtime panic。同步信号需要事件及事件时间,相反异步信号不需要于时钟同步,异步信号是系统内核或其它程序发送的信号。举例,SIGHUP表示它控制的终端被关闭,常用的是当按CTRL+C时,会发送SIGINT信号。当SIGINT信号有键盘产生时,实际上不是终止应用程序,而是生成特定键盘中断,并且处理该中断的程序开始执行,其缺省行为时关闭应用程序,但我们可以覆盖缺省行为,写我们自己的业务处理逻辑。

事实上许多信号处理都可以被覆盖,Go开发者可以编写自己的处理程序来自定义信号处理行为。然而,除非有非常好的理由,否则改变信号的默认行为并不是明智的想法。这里引用列举常用linux信号:

SIGHUP: ‘HUP’ = ‘hung up’. This signal is generated when a controlling process dies or hangs up detected on the controlling terminal.
SIGINT: When a process is interrupted from keyboard by pressing CTRL+C
SIGQUIT: Quit from keyboard
SIGILL: Illegal instruction. A synonym for SIGPWR() – power failure
SIGABRT: Program calls the abort() function – an emergency stop.
SIGBUS: Bad memory access. Attempt was made to access memory inappropriately
SIGFPE: FPE = Floating point exception
SIGKILL: The kill signal. The process was explicitly killed.
SIGUSR1: This signal is open for programmers to write a custom behavior.
SIGSEGV: Invalid memory reference. In C when we try to access memory beyond array limit, this signal is generated.
SIGUSR2: This signal is open for programmers to write a custom behavior.
SIGPIPE: This signals us open for programmers to write a custom behavior.
SIGALRM: Process requested a wake up call by the operating system such as by calling the alarm() function.
SIGTERM: A process is killed

处理多个信号

下面再看一个示例,较上面的示例增加了对SIGTERM信号的处理:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main
 
import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)
 
func handler(signal os.Signal) {
    if signal == syscall.SIGTERM {
        fmt.Println("Got kill signal. ")
        fmt.Println("Program will terminate now.")
        os.Exit(0)
    } else if signal == syscall.SIGINT {
        fmt.Println("Got CTRL+C signal")
        fmt.Println("Closing.")
        os.Exit(0)
    } else {
        fmt.Println("Ignoring signal: ", signal)
    }
}
 
func main() {
    sigchnl := make(chan os.Signal, 1)
    signal.Notify(sigchnl)
    // signal.Notify(sigchnl, os.Interrupt, syscall.SIGTERM)
    exitchnl := make(chan int)
 
    go func() {
        for {
            s := <-sigchnl
            handler(s)
        }
    }()
 
    exitcode := <-exitchnl
    os.Exit(exitcode)
}

首先创建通道接收响应信号,我们也可以指定信号类型:signal.Notify(sigchnl, os.Interrupt, syscall.SIGTERM),否则会接收所有信号,包括命令窗口改变大小信号。然后在handle函数判断信号类型并分别进行处理。

程序运行后,linux可以通过ps -h 查看go程序,然后执行kill命令。

NotifyContext示例

Go.1.16提供了signal.NotifyContext函数,可以实现更优雅的关闭功能。下面示例实现简单web服务器,示例handle需要10s处理时间,如果正在运行的web服务,客户端通过postman或cURL发送请求,然后在服务端立刻通过ctrl+C发送终止信号。
我们希望看到服务器在终止之前通过响应终止请求而优雅地关闭。如果关闭时间过长,可以发送另一个中断信号立即退出,或者暂停将在5秒后开始。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main
 
import (
    "context"
    "fmt"
    "net/http"
    "os"
    "os/signal"
    "time"
)
 
var (
    server http.Server
)
 
func main() {
    // Create context that listens for the interrupt signal from the OS.
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
    defer stop()
 
    server = http.Server{
        Addr: ":8080",
    }
 
    // Perform application startup.
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(time.Second * 10)
        fmt.Fprint(w, "Hello world!")
    })
 
    // Listen on a different Goroutine so the application doesn't stop here.
    go server.ListenAndServe()
 
    // Listen for the interrupt signal.
    <-ctx.Done()
 
    // Restore default behavior on the interrupt signal and notify user of shutdown.
    stop()
    fmt.Println("shutting down gracefully, press Ctrl+C again to force")
 
    // Perform application shutdown with a maximum timeout of 5 seconds.
    timeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
 
    if err := server.Shutdown(timeoutCtx); err != nil {
        fmt.Println(err)
    }
}

总结

本文介绍了信号的概念及常用信号,并给出了应用广泛的几个示例,例如优雅地关闭应用服务、在命令行应用中接收终止命令。

到此这篇关于拦截信号Golang应用优雅关闭的文章就介绍到这了,更多相关Golang关闭内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/neweastsun/article/details/128812053

延伸 · 阅读

精彩推荐
  • GolangGo语言Cookie用法分析

    Go语言Cookie用法分析

    这篇文章主要介绍了Go语言Cookie用法,结合实例形式分析了Go语言Cookie的设置、读取等相关操作技巧,需要的朋友可以参考下 ...

    52PHP7122020-05-05
  • GolangGolang Http 验证码示例实现

    Golang Http 验证码示例实现

    这篇文章主要介绍了Golang Http 验证码示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小...

    xialeistudio4832020-09-06
  • Golangmayfly-go部署和使用详解

    mayfly-go部署和使用详解

    这篇文章主要介绍了mayfly-go部署和使用详解,此处部署基于CentOS7.4部署,结合实例代码图文给大家讲解的非常详细,需要的朋友可以参考下...

    LinuxLOVERes5622022-11-21
  • Golanggolang操作mongodb的方法

    golang操作mongodb的方法

    这篇文章主要介绍了golang操作mongodb的方法,涉及Go语言操作mongodb的连接、读取及显示的方法,具有一定参考借鉴价值,需要的朋友可以参考下 ...

    脚本之家3242020-04-16
  • Golanggo单例实现双重检测是否安全的示例代码

    go单例实现双重检测是否安全的示例代码

    这篇文章主要介绍了go单例实现双重检测是否安全,本文给大家分享双重检验示例代码,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要...

    NO0b3742022-09-05
  • GolangGoLang unsafe包详细讲解

    GoLang unsafe包详细讲解

    从golang的定义来看,unsafe 是类型安全的操作。顾名思义,它应该非常谨慎地使用; unsafe可能很危险,但也可能非常有用。例如,当使用系统调用和Go结构必...

    ~庞贝10382022-11-28
  • GolangGolang算法之田忌赛马问题实现方法分析

    Golang算法之田忌赛马问题实现方法分析

    这篇文章主要介绍了Golang算法之田忌赛马问题实现方法,结合具体实例形式分析了基于Go语言的田忌赛马问题原理与算法实现技巧,需要的朋友可以参考下 ...

    books19582162020-05-05
  • Golang解决vscode中golang插件依赖安装失败问题

    解决vscode中golang插件依赖安装失败问题

    这篇文章主要介绍了解决vscode中golang插件依赖安装失败问题,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下 ...

    feiquan13462020-05-27