Golang 程序如何优雅退出
当我们由应用程序升级时,我们一般是重启服务进行加载最新代码,如果我们想让当前应用把程序正在处理的任务处理完成再退出进行优雅的重启,不丢失当前处理的任务。
实现方法
监听SIGTERM信号
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt)
go func() {
select {
case sig := <-c:
fmt.Printf("Got %s signal. Aborting...\n", sig)
os.Exit(1)
}
}()
下面我们来看下如何对一个程序进行改造,第一个是没有监听任何SIGTERM信号的程序
这个程序在Ctrl+C
之后就直接退出了
package main
import (
"fmt"
"time"
)
type Task struct {
ticker *time.Ticker
}
func (t *Task) Run() {
for {
select {
case <-t.ticker.C:
handle()
}
}
}
func handle() {
for i := 0; i < 5; i++ {
fmt.Print("#")
time.Sleep(time.Millisecond * 200)
}
fmt.Println()
}
func main() {
task := &Task{
ticker: time.NewTicker(time.Second * 2),
}
task.Run()
}
如果我们想对Ctrl+C
之后不马上退出,而是处理玩当前任务再退出,我们可以这样修改
func main() {
task := &Task{
ticker: time.NewTicker(time.Second * 2),
}
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt)
go func() {
select {
case sig := <-c:
fmt.Printf("Got %s signal. Aborting...\n", sig)
// 等待10s,处理完当前的任务
time.Sleep(time.Second * 10)
os.Exit(1)
}
}()
task.Run(
以上模拟虽然等待了10s再退出,但是仍然程序有可能还有正在处理的任务
假如我们想更优雅的退出呢?我们可以在Task加上两个成员,此时Task结构如下
type Task struct {
// 收到了退出的消息
closed chan struct{}
// 等待所有的协程退出
wg sync.WaitGroup
ticker *time.Ticker
}
然后我们整个程序调整为
package main
import (
"fmt"
"math/rand"
"os"
"os/signal"
"sync"
"time"
)
// Task ...
type Task struct {
closed chan struct{}
wg sync.WaitGroup
ticker *time.Ticker
}
// Run ...
func (t *Task) Run() {
for {
select {
// 这里收到了退出消息之后,就不不会再调用handle了
case <-t.closed:
fmt.Println("close .....")
return
case <-t.ticker.C:
t.wg.Add(1)
fmt.Println("add handle.....")
go handle(t)
}
}
}
// Stop ...
func (t *Task) Stop() {
fmt.Println("got close sig.....")
close(t.closed)
//在这里会等待所有的协程都退出
t.wg.Wait()
fmt.Println("all goroutine done...")
}
func handle(task *Task) {
defer task.wg.Done()
for i := 0; i < 5; i++ {
fmt.Print("#")
st := RandInt64(1, 5)
time.Sleep(time.Second * time.Duration(st))
}
fmt.Println()
}
func main() {
task := &Task{
closed: make(chan struct{}),
ticker: time.NewTicker(time.Second * 2),
}
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt)
go task.Run()
// 主程序在监听`Ctrl+C`消息,收到之后,会把task任务stop
select {
case sig := <-c:
fmt.Printf("Got %s signal. Aborting...\n", sig)
task.Stop()
}
}
// RandInt64 ...
func RandInt64(min, max int64) int64 {
if min >= max || min == 0 || max == 0 {
return max
}
return rand.Int63n(max-min) + min
}
运行结果
dd handle.....
#add handle.....
##add handle.....
##add handle.....
####add handle.....
######add handle.....
###^CGot interrupt signal. Aborting...
got close sig.....
close .....
#
########
#
#
#
all goroutine done...
exit.....