退出运行中的程序,可以粗暴的kill -9 $PID,但这样会破坏业务的完整性,有可能一个正在在执行的逻辑半途而费,从而产生不正常的垃圾数据。

本文总结在go语言中,如何能优雅的退出网络应用,涉及的知识包括:signal,channel,WaitGroup等。

从这里:https://gobyexample.com/channel-synchronization 可以简单了解到,在go中如何使用channel实现goroutines同步。

在nsq中,也使用了相同的机制,不过封装更复杂了些。我们以nsqadmin中的实现为例进行简单的分析。

代码段1(来自:https://github.com/bitly/nsq/blob/master/nsqadmin/main.go):

exitChan := make(chan int)

signalChan := make(chan os.Signal, 1)

go func() {

<-signalChan

exitChan <- 1

}()

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

//....

nsqadmin.Main()

<-exitChan

nsqadmin.Exit()

上面的代码正常执行后,会卡到倒数第二句的<-exitChan中,直到exitChan中有数据进入。

当我们通过命令行执行:kill -s SIGINT $PID时,signalChan中收到一个信息,上面第四行代码中的goroutines会停止阻塞,继续向下执行,exitChann中加入一条数据。当exitChann中有了数据,倒数第二句也会停止阻塞,执行nsqadmin.Exit()实现优雅退出。

当然上面的代码,也可以简化:

signalChan := make(chan os.Signal, 1)

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

//....

nsqadmin.Main()

<-signalChan

nsqadmin.Exit()

至于nsq为什么没有这么做,还不太清楚。能力有限,体会不到其深意所在。

上面的例子只适用于两个goroutines之间,一个处理完业务后给exitChan写数据,主goroutines卡在exitChan上等数据。(主goroutines必须比处理业务的goroutines后退出)。

如果一个主线程下开了多个子goroutines,使用channel的方式就不够优雅了。可以使用WaitGroup,关于WaitGroup的介绍可以参考:http://www.baiyuxiong.com/?p=913

在nsq中同样使用了WaitGroup实现退出。

代码段2(来自:https://github.com/bitly/nsq/blob/master/util/wait_group_wrapper.go)

type WaitGroupWrapper struct {

sync.WaitGroup

}

func (w *WaitGroupWrapper) Wrap(cb func()) {

w.Add(1)

go func() {

cb()

w.Done()

}()

}

代码段3(来自:<a href="https://github.com/bitly/nsq/blob/master/nsqadmin/nsqadmin.go">https://github.com/bitly/nsq/blob/master/nsqadmin/nsqadmin.go</a>)

func (n *NSQAdmin) Main() {

httpListener, err := net.Listen("tcp", n.httpAddr.String())

if err != nil {

n.logf("FATAL: listen (%s) failed - %s", n.httpAddr, err)

os.Exit(1)

}

n.httpListener = httpListener

httpServer := NewHTTPServer(&Context{n})

n.waitGroup.Wrap(func() {

util.HTTPServer(n.httpListener, httpServer, n.opts.Logger, "HTTP")

})

n.waitGroup.Wrap(func() { n.handleAdminActions() })

}

func (n *NSQAdmin) Exit() {

n.httpListener.Close()

close(n.notifications)

n.waitGroup.Wait()

}

在代码段2中,对waitGroup进行了简单封装,开启goroutines前计数加1,执行完计数减1。

代码段3中,Main()方法里,调用了两次waitGroup.Wrap()方法,参考代码段2可以知道,这会启动两个子goroutines,并使waitGroup计数加2.而子goroutines中使用了http包监听网络服务,阻塞goroutines,使得计数减1的操作不能被调用。

在我们的代码段1中可以知道,命令行发送了kill以后,会执行代码段3的Exit()方法,当方法里的n.httpListener.Close()被调用后,网络服务中断,代码段2中的阻塞就会停止,进而执行计数减1的,当两个子goroutines中计数各减1以后。Exit()方法中的n.waitGroup.Wait()就会继续执行,主线程结束,程序退出。

最新文章

  1. Java总结篇系列:Java多线程(一)
  2. Pressure-sensitive pores scattered
  3. 指针与const
  4. Centos环境下部署游戏服务器-软件安装
  5. Spring多资源文件properties的配置
  6. 【转载】React初学者入门须知
  7. 用HTML5 Canvas 做擦除及扩散效果
  8. YY前端笔试总结
  9. CII-原子
  10. 最受欢迎web前端技术总结
  11. Nginx教程(三) Nginx日志管理
  12. ReactiveCocoa源码解析(四) Signal中的静态属性静态方法以及面向协议扩展
  13. Linux下自动化监控内存、存储空间!
  14. spring boot 添加拦截器的简单实例(springBoot 2.x版本,添加拦截器,静态资源不可访问解决方法)
  15. 数据结构与算法(C#)入门 --- 序
  16. oo第四次博客总结
  17. Java反射、动态加载(将java类名、方法、方法参数当做参数传递,执行方法)
  18. 通过脚手架创建Vue项目
  19. PHP编程基础学习(一)——数据类型
  20. tmux安装

热门文章

  1. Greys--JVM异常诊断工具
  2. html5打开摄像头并用canvas模拟拍照 - 转
  3. android Activity中设置setResult 没有触发onActivityResult
  4. codeforces820B Mister B and Angle in Polygon 2017-06-28 09:42 123人阅读 评论(0) 收藏
  5. poj2481
  6. 8.ajax与django后台json数据的交互
  7. cvpr2015总结
  8. 【Unity】UGUI系列教程——拼接一个简单界面
  9. .net core在vs开发环境下脱离iis运行
  10. 使用Code First建模自引用关系笔记