Golang语言中的系统Signal处理

我们在生产环境下运行的系统要求优雅退出,即程序接收退出通知后,会有机会先执行一段清理代码,将收尾工作做完后再真正退出。我们采用系统Signal来 通知系统退出,即kill pragram-pid。我们在程序中针对一些系统信号设置了处理函数,当收到信号后,会执行相关清理程序或通知各个子进程做自清理。kill -9强制杀掉程序是不能被接受的,那样会导致某些处理过程被强制中断,留下无法恢复的现场,导致消息被破坏,影响下次系统启动运行。

最近用Golang实现的一个代理程序也需要优雅退出,因此我尝试了解了一下Golang中对系统Signal的处理方式,这里和大家分享。Golang 的系统信号处理主要涉及os包、os.signal包以及syscall包。其中最主要的函数是signal包中的Notify函数:

func Notify(c chan<- os.Signal, sig …os.Signal)

该函数会将进程收到的系统Signal转发给channel c。转发哪些信号由该函数的可变参数决定,如果你没有传入sig参数,那么Notify会将系统收到的所有信号转发给c。如果你像下面这样调用Notify:

signal.Notify(c, syscall.SIGINT, syscall.SIGUSR1, syscall.SIGUSR2)

则Go只会关注你传入的Signal类型,其他Signal将会按照默认方式处理,大多都是进程退出。因此你需要在Notify中传入你要关注和处理的Signal类型,也就是拦截它们,提供自定义处理函数来改变它们的行为。

下面是一个较为完整的例子:

//signal.go

package main

import "fmt" import "time" import "os" import "os/signal" import "syscall"

type signalHandler func(s os.Signal, arg interface{})

type signalSet struct { m map[os.Signal]signalHandler }

func signalSetNew()(*signalSet){ ss := new(signalSet) ss.m = make(map[os.Signal]signalHandler) return ss }

func (set *signalSet) register(s os.Signal, handler signalHandler) { if _, found := set.m[s]; !found { set.m[s] = handler } }

func (set *signalSet) handle(sig os.Signal, arg interface{})(err error) { if _, found := set.m[sig]; found { set.m[sig](sig, arg) return nil } else { return fmt.Errorf("No handler available for signal %v", sig) }

panic("won't reach here") }

func main() { go sysSignalHandleDemo() time.Sleep(time.Hour) // make the main goroutine wait! }

func sysSignalHandleDemo() { ss := signalSetNew() handler := func(s os.Signal, arg interface{}) { fmt.Printf("handle signal: %v\n", s) }

ss.register(syscall.SIGINT, handler) ss.register(syscall.SIGUSR1, handler) ss.register(syscall.SIGUSR2, handler)

for { c := make(chan os.Signal) var sigs []os.Signal for sig := range ss.m { sigs = append(sigs, sig) } signal.Notify(c) sig := <-c

err := ss.handle(sig, nil) if (err != nil) { fmt.Printf("unknown signal received: %v\n", sig) os.Exit(1) } } }

上例中Notify函数只有一个参数,没有传入要关注的sig,因此程序会将收到的所有类型Signal都转发到channel c中。build该源文件并执行程序:

$> go build signal.go $> signal

在另外一个窗口下执行如下命令: $> ps -ef|grep signal tonybai 25271 1087 0 16:27 pts/1 00:00:00 signal $> kill -n 2 25271 $> kill -n 12 25271 $> kill 25271

我们在第一个窗口会看到如下输出: $> signal handle signal: interrupt handle signal: user defined signal 2 unknown signal received: terminated

在sysSignalHandleDemo中我们也可以为Notify传入我们所关注的Signal集合:

signal.Notify(c, sigs…)

这样只有在该集合中的信号我们才能捕获,收到未在集合中的信号时,程序多直接退出。上面只是一个Demo,只是说明了我们可以捕捉到我们所关注的信号,并未体现程序如何优雅退出,不同程序的退出方式不同,这里没有通用方法,就不细说了,你的程序需要你专门的设计。

另外我们生产环境下的程序多是以Daemon守护进程的形式运行的。我们用C实现的程序多参考“Unix高级编程”中的方法将程序转为Daemon Process,但在Go中目前尚提供相关方式,网上有一些实现,但据说都不理想。更多的Go开发者建议不要在代码中实现Daemon转换,建议直接利用 第三方工具。比如在Ubuntu下我们可以使用start-stop-daemon这个小程序轻松将你的程序转换为Daemon:

$> start-stop-daemon –start –pidfile ./signal.pid –startas /home/tonybai/test/go/signal –background -m $> start-stop-daemon –stop –pidfile ./signal.pid –startas /home/tonybai/test/go/signal

这里注意:只有加上-m选项,pidfile才能成功创建。

start-stop-daemon在Debian系的Linux发行版中都是默认自带的。但在Redhat系Linux发行版中却没有该工具,我们可以自行安装:

wget -c http://developer.axis.com/download/distribution/apps-sys-utils-start-stop-daemon-IR1_9_18-2.tar.gz tar -xzf apps-sys-utils-start-stop-daemon-IR1_9_18-2.tar.gz cd apps/sys-utils/start-stop-daemon-IR1_9_18-2 gcc start-stop-daemon.c -o start-stop-daemon

切换到root下 cp start-stop-daemon /sbin/ chmod +x /sbin/start-stop-daemon

另外Go 1.0.2提供的二进制安装包直接在Redhat 5.6(Linux tonybai 2.6.18-238.el5 #1 SMP Sun Dec 19 14:22:44 EST 2010 x86_64 x86_64 x86_64 GNU/Linux)下面运行出错,提示无法找到GLIBC 2.7版本。目前解决这一问题的方法似乎只有从源码编译安装。进入到$GOROOT/src下,执行./all.bash即可。重现编译链接后的go可执 行程序则运行一切正常。

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2016-01-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏代码世界

Python之IO模型

IO模型介绍 为了更好地了解IO模型,我们需要事先回顾下:同步、异步、阻塞、非阻塞     同步(synchronous) IO和异步(asynchronous...

450110
来自专栏编程软文

开发过程中快速抓包并解析

这几天小编在工作中遇到了一个灵异事件,客户端使用的是安卓原生系统,服务端使用的是java。需求就是客户端在照相的时候可以实时上传照片。后台接收并保存,并且可以在...

28830
来自专栏JAVA高级架构

2018年一线互联网公司Java高级面试题总结

1、hashcode相等两个类一定相等吗?equals呢?相反呢? 2、介绍一下集合框架? 3、hashmap hastable 底层实现什么区别?hashta...

60180
来自专栏逆向技术

Win32之内存管理之虚拟内存跟物理内存

  我们知道每个应用程序都有自己独立的4GB空间.  假设A进程的 地址123 存储了10  那么B进程的123地址 存储了20

19440
来自专栏xingoo, 一个梦想做发明家的程序员

Volatile的作用

众所周知,volatile关键字可以让线程的修改立刻通知其他的线程,从而达到数据一致的作用。那么它具体涉及到哪些内容呢? 关于缓存 计算机最大的存储空间就...

22680
来自专栏Java后端技术栈

基于TCP和HTTP协议的RPC简单实现

(1)RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网...

12630
来自专栏听雨堂

VPC下访问FTP的问题

        最近用VPC一直用的很爽,用来调试安装包,实在太好用了。但是,最近却遇到一个问题,FTP总是无法正常工作,经过漫长而痛苦的跟踪定位,找到原因: ...

26780
来自专栏北京马哥教育

15个Linux文件传输命令

? 文 | 糖豆 来源 | 菜鸟教程 糖豆贴心提醒,本文阅读时间5分钟,文末有秘密! Linux lprm命令 Linux lprm命令用于将一个工作...

37050
来自专栏皮振伟的专栏

[linux][statethread]协程库ST技术分析

前言: 在IO密集型的场景下,尤其是互联网后台,经常会使用epoll等IO复用技术。鉴于直接使用epoll的代码阅读性和开发效率等原因,就抽象出来了各种高级模型...

35080
来自专栏崔庆才的专栏

让面试官颤抖的 HTTP 2.0 协议面试题

Http协议,对于拥有丰富开发经验的程序员来说简直是信手拈来,家常便饭。虽然天天见,但是对于http协议的问题,可能很多人在没有积极准备的情况下,不一定能很好的...

29530

扫码关注云+社区

领取腾讯云代金券