热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Golang信号处理及如何实现进程的优雅退出详解

这篇文章主要给大家介绍了关于Golang信号处理及如何实现进程的优雅退出的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。

Linux系统中的信号类型

各操作系统的信号定义或许有些不同。下面列出了POSIX中定义的信号。

在linux中使用34-64信号用作实时系统中。

命令 man 7 signal 提供了官方的信号介绍。也可以是用kill -l来快速查看

列表中,编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。

Linux支持的标准信号有以下一些,一个信号有多个值的是因为不同架构使用的值不一样,比如x86, ia64,ppc, s390, 有3个值的,第一个值是slpha和sparc,中间的值是 ix86,ia64, ppc, s390, arm和sh, 最后一个值是对mips的,连字符-表示这个架构是缺这个信号支持的,

第1列为信号名;

第2列为对应的信号值,需要注意的是,有些信号名对应着3个信号值,这是因为这些信号值与平台相关,将man手册中对3个信号值的说明摘出如下,the first one is usually valid for alpha and sparc, the middle one for i386, ppc and sh, and the last one for mips.

第3列为操作系统收到信号后的动作,Term表明默认动作为终止进程,Ign表明默认动作为忽略该信号,Core表明默认动作为终止进程同时输出core dump,Stop表明默认动作为停止进程。

第4列为对信号作用的注释性说明。

标准信号-POSIX.1-1990定义

 Signal Value Action Comment
 ----------------------------------------------------------------------
 SIGHUP 1 Term Hangup detected on controlling terminal
     or death of controlling process
 SIGINT 2 Term Interrupt from keyboard
 SIGQUIT 3 Core Quit from keyboard
 SIGILL 4 Core Illegal Instruction
 SIGABRT 6 Core Abort signal from abort(3)
 SIGFPE 8 Core Floating point exception
 SIGKILL 9 Term Kill signal
 SIGSEGV 11 Core Invalid memory reference
 SIGPIPE 13 Term Broken pipe: write to pipe with no
     readers
 SIGALRM 14 Term Timer signal from alarm(2)
 SIGTERM 15 Term Termination signal
 SIGUSR1 30,10,16 Term User-defined signal 1
 SIGUSR2 31,12,17 Term User-defined signal 2
 SIGCHLD 20,17,18 Ign Child stopped or terminated
 SIGCONT 19,18,25 Cont Continue if stopped
 SIGSTOP 17,19,23 Stop Stop process
 SIGTSTP 18,20,24 Stop Stop typed at tty
 SIGTTIN 21,21,26 Stop tty input for background process
 SIGTTOU 22,22,27 Stop tty output for background process

SIGKILL和SIGSTOP信号是不能被捕获,阻塞和忽略的。

标准信号-SUSv2 and POSIX.1-2001定义

 Signal Value Action Comment
 --------------------------------------------------------------------
 SIGBUS 10,7,10 Core Bus error (bad memory access)
 SIGPOLL   Term Pollable event (Sys V).
     Synonym for SIGIO
 SIGPROF 27,27,29 Term Profiling timer expired
 SIGSYS 12,-,12 Core Bad argument to routine (SVr4)
 SIGTRAP 5 Core Trace/breakpoint trap
 SIGURG 16,23,21 Ign Urgent condition on socket (4.2BSD)
 SIGVTALRM 26,26,28 Term Virtual alarm clock (4.2BSD)
 SIGXCPU 24,24,30 Core CPU time limit exceeded (4.2BSD)
 SIGXFSZ 25,25,31 Core File size limit exceeded (4.2BSD)

早在Linux 2.2SIGSYS, SIGXCPU, SIGXFSZ和SIGBUS(非sparc和mips架构)的默认操作就是终止进程(但是不产生coredump)

在一些unix系统中SIGXCPU和SIGXFSZ信号是用来终止进程的,也是不产生coredunp,从Linux 2.4开始这些信号会产生coredump了。

标准信号-其它信号

  Signal  Value  Action Comment
  --------------------------------------------------------------------
  SIGIOT   6  Core IOT trap. A synonym for SIGABRT
  SIGEMT  7,-,7  Term
  SIGSTKFLT -,16,-  Term Stack fault on coprocessor (unused)
  SIGIO  23,29,22 Term I/O now possible (4.2BSD)
  SIGCLD  -,-,18  Ign  A synonym for SIGCHLD
  SIGPWR  29,30,19 Term Power failure (System V)
  SIGINFO  29,-,-    A synonym for SIGPWR
  SIGLOST  -,-,-  Term File lock lost
  SIGWINCH 28,28,20 Ign  Window resize signal (4.3BSD, Sun)
  SIGUNUSED -,31,-  Term Unused signal (will be SIGSYS)

信号29是在alpha中是 SIGINFO或SIGPWR,但是在sparc中是SIGLOST。

SIGEMT没有在POSIX.1-2001中定义, 但是在大多数Unix戏中是没有的,他的默认处理方式是coredump并且终止进程。
SIGPWR(没有在POSIX.1-2001中定义)他的默认处理方式是忽略。

SIGIO(没有在POSIX.1-2001中定义)在一些Unix系统中的处理方式也是忽略。

kill pid的作用是向进程号为pid的进程发送SIGTERM(这是kill默认发送的信号),该信号是一个结束进程的信号且可以被应用程序捕获。若应用程序没有捕获并响应该信号的逻辑代码,则该信号的默认动作是kill掉进程。这是终止指定进程的推荐做法。

kill -9 pid则是向进程号为pid的进程发送SIGKILL(该信号的编号为9),从本文上面的说明可知,SIGKILL既不能被应用程序捕获,也不能被阻塞或忽略,其动作是立即结束指定进程。通俗地说,应用程序根本无法“感知”SIGKILL信号,它在完全无准备的情况下,就被收到SIGKILL信号的操作系统给干掉了,显然,在这种“暴力”情况下,应用程序完全没有释放当前占用资源的机会。事实上,SIGKILL信号是直接发给init进程的,它收到该信号后,负责终止pid指定的进程。在某些情况下(如进程已经hang死,无法响应正常信号),就可以使用kill -9来结束进程。

若通过kill结束的进程是一个创建过子进程的父进程,则其子进程就会成为孤儿进程(Orphan Process),这种情况下,子进程的退出状态就不能再被应用进程捕获(因为作为父进程的应用程序已经不存在了),不过应该不会对整个linux系统产生什么不利影响。

Go中的信号发送和处理

有时候我们想在Go程序中处理Signal信号,比如收到 SIGTERM 信号后优雅的关闭程序(参看下一节的应用)。Go信号通知机制可以通过往一个channel中发送 os.Signal 实现。首先我们创建一个os.Signal channel,然后使用 signal.Notify 注册要接收的信号。

package main
import (
 "fmt"
 "os"
 "os/signal"
 "syscall"
)
func main() {
 sigs := make(chan os.Signal, 1)
 done := make(chan bool, 1)
 // signal.Notify(c)
 signal.Notify(sigs, os.Interrupt, os.Kill, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGINT, syscall.SIGTERM)
 go func() {
 sig := <-sigs
 fmt.Println(sig)
 done <- true
 }()

 fmt.Println("wait for signal")
 <- done
 fmt.Println("got signal and exit")
 fmt.Println("run done")
}

如何实现进程的优雅退出

首先什么是优雅退出呢?所谓的优雅退出,其实就是避免暴力杀死进程,让进程在接收到信号之后,自动的做一些善后处理,再自己自愿的退出。

Linux Server端的应用程序经常会长时间运行,在运行过程中,可能申请了很多系统资源,也可能保存了很多状态,在这些场景下,我们希望进程在退出前,可以释放资源或将当前状态dump到磁盘上或打印一些重要的日志,也就是希望进程优雅退出(exit gracefully)。

从上面的介绍不难看出,优雅退出可以通过捕获SIGTERM来实现。具体来讲,通常只需要两步动作:

1)注册SIGTERM信号的处理函数并在处理函数中做一些进程退出的准备。信号处理函数的注册可以通过signal()sigaction()来实现,其中,推荐使用后者来实现信号响应函数的设置。信号处理函数的逻辑越简单越好,通常的做法是在该函数中设置一个bool型的flag变量以表明进程收到了SIGTERM信号,准备退出。

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

这个在我前面的一篇文章中也介绍过【=[golang的httpserver优雅重启][1]】https://www.jb51.net/article/137069.htm,里面介绍了一般我们使用的httpserver如何做到优雅重启,这里面也介绍了一些信号的使用,和优雅重启的思路。今天这里我们介绍的是如何优雅退出,其实是优雅重启的一个简化版。

package main
import (
 "fmt"
 "os"
 "os/signal"
 "syscall"
 "time"
)
 
func main() {
 sigs := make(chan os.Signal, 1)
 // done := make(chan bool, 1) 
 // signal.Notify(sigs)
 // signal.Notify(sigs, os.Interrupt, os.Kill, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGINT, syscall.SIGTERM)
 signal.Notify(sigs, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT) 
 // go func() {
 // sig := <-sigs
 // fmt.Println(sig)
 // done <- true
 // }()
 go func() {
 for s := range sigs {
 switch s {
 case syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT:
 fmt.Println("got signal and try to exit: ", s)
 do_exit()
 case syscall.SIGUSR1:
 fmt.Println("usr1: ", s)
 case syscall.SIGUSR2:
 fmt.Println("usr2: ", s)
 default:
 fmt.Println("other: ", s)
 }
 }
 }()
 
 
 fmt.Println("wait for signal")
 i := 0
 for {
 i++
 fmt.Println("times: ", i)
 time.Sleep(1 * time.Second)
 }
 // <- done
 fmt.Println("got signal and exit")
 fmt.Println("run done")
}
 
func do_exit() {
 fmt.Println("try do some clear jobs")
 fmt.Println("run done")
 os.Exit(0)
}
kill -USR1 pid 
usr1 user defined signal 1
 
kill -USR2 pid 
usr2 user defined signal 2
 
kill -QUIT pid 
got signal and try to exit: quit
try do some clear jobs
run done

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。


推荐阅读
  • 如何使用PLEX播放组播、抓取信号源以及设置路由器
    本文介绍了如何使用PLEX播放组播、抓取信号源以及设置路由器。通过使用xTeve软件和M3U源,用户可以在PLEX上实现直播功能,并且可以自动匹配EPG信息和定时录制节目。同时,本文还提供了从华为itv盒子提取组播地址的方法以及如何在ASUS固件路由器上设置IPTV。在使用PLEX之前,建议先使用VLC测试是否可以正常播放UDPXY转发的iptv流。最后,本文还介绍了docker版xTeve的设置方法。 ... [详细]
  • x86 linux的进程调度,x86体系结构下Linux2.6.26的进程调度和切换
    进程调度相关数据结构task_structtask_struct是进程在内核中对应的数据结构,它标识了进程的状态等各项信息。其中有一项thread_struct结构的 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • 【shell】网络处理:判断IP是否在网段、两个ip是否同网段、IP地址范围、网段包含关系
    本文介绍了使用shell脚本判断IP是否在同一网段、判断IP地址是否在某个范围内、计算IP地址范围、判断网段之间的包含关系的方法和原理。通过对IP和掩码进行与计算,可以判断两个IP是否在同一网段。同时,还提供了一段用于验证IP地址的正则表达式和判断特殊IP地址的方法。 ... [详细]
  • 嵌入式处理器的架构与内核发展历程
    本文主要介绍了嵌入式处理器的架构与内核发展历程,包括不同架构的指令集的变化,以及内核的流水线和结构。通过对ARM架构的分析,可以更好地理解嵌入式处理器的架构与内核的关系。 ... [详细]
  • 本文概述了JNI的原理以及常用方法。JNI提供了一种Java字节码调用C/C++的解决方案,但引用类型不能直接在Native层使用,需要进行类型转化。多维数组(包括二维数组)都是引用类型,需要使用jobjectArray类型来存取其值。此外,由于Java支持函数重载,根据函数名无法找到对应的JNI函数,因此介绍了JNI函数签名信息的解决方案。 ... [详细]
  • 原文地址http://balau82.wordpress.com/2010/02/28/hello-world-for-bare-metal-arm-using-qemu/最开始时 ... [详细]
  • 很多时候在注册一些比较重要的帐号,或者使用一些比较重要的接口的时候,需要使用到随机字符串,为了方便,我们设计这个脚本需要注意 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • Python语法上的区别及注意事项
    本文介绍了Python2x和Python3x在语法上的区别,包括print语句的变化、除法运算结果的不同、raw_input函数的替代、class写法的变化等。同时还介绍了Python脚本的解释程序的指定方法,以及在不同版本的Python中如何执行脚本。对于想要学习Python的人来说,本文提供了一些注意事项和技巧。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • Centos7搭建ELK(Elasticsearch、Logstash、Kibana)教程及注意事项
    本文介绍了在Centos7上搭建ELK(Elasticsearch、Logstash、Kibana)的详细步骤,包括下载安装包、安装Elasticsearch、创建用户、修改配置文件等。同时提供了使用华为镜像站下载安装包的方法,并强调了保证版本一致的重要性。 ... [详细]
  • PG12新增的VACUUM命令的SKIP_LOCKED选项
    PG12版本的VACUUM命令新增了SKIP_LOCKED选项,该选项使得vacuum命令在遇到被lock住的table时可以跳过并被视为成功执行。之前的版本中,vacuum命令会一直处于等待状态。本文还提到了PostgreSQL 12.1版本的相关信息。 ... [详细]
  • Mono为何能跨平台
    概念JIT编译(JITcompilation),运行时需要代码时,将Microsoft中间语言(MSIL)转换为机器码的编译。CLR(CommonLa ... [详细]
author-avatar
天之苍
这个家伙很懒,什么也没留下!