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.....