package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func Logic() {
for {
time.Sleep(time.Second*2)
fmt.Println("logic time=",time.Now().Unix())
}
}
func main() {
// 逻辑处理协程
go Logic()
// 监控两个信号
// TERM信号(kill + 进程号 触发)
// 中断信号(ctrl + c 触发)
osc := make(chan os.Signal, 1)
signal.Notify(osc,syscall.SIGTERM, syscall.SIGINT)
s := <- osc
fmt.Println("监听到退出信号,s=",s)
// 退出前的清理操作
// clean()
fmt.Println("main程序退出")
}
通过go1.16+的NotifyContext方法和errgroup包实现服务的优雅停止
package main
import (
"context"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
"log"
"net/http"
"os/signal"
"syscall"
"time"
)
type ginHttp struct {
server *http.Server
}
func NewGinHttpServer(listenIp string, listenPort int, f func(engine *gin.Engine)) *ginHttp {
engine := gin.Default()
f(engine)
s := &http.Server{
Addr: fmt.Sprintf("%s:%d", listenIp, listenPort),
Handler: engine,
}
return &ginHttp{
server: s,
}
}
func (g *ginHttp) Serve() <-chan error {
// chan长度大于等于1, 不然errch不存在读取取,将永远阻塞在这里,造成goroutine泄露
errch := make(chan error, 1)
go func() {
err := g.server.ListenAndServe()
if err != nil {
errch <- err
log.Printf("服务停止:%v", err)
}
close(errch)
}()
return errch
}
func (g *ginHttp) Stop() error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
return g.server.Shutdown(ctx)
}
func main() {
// 创建通过监听信号syscall.SIGINT, syscall.SIGEMT来停止的context
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGEMT)
defer cancel()
// 通过errgroup来运行多个服务
g, ctx := errgroup.WithContext(ctx)
// 服务一
g.Go(func() error {
tr := time.NewTimer(time.Second * 30)
select {
// 监听context是否被取消, 取消则终止服务
case <-ctx.Done():
/*
停止服务操作
stop()
*/
log.Println("收到context取消信号,停止服务一")
return ctx.Err()
// 模拟服务
case <-tr.C:
// 模拟30秒后服务出错返回, errgroup会对errgroup.WithContext返回的context进行取消
log.Println("服务一出错: xxx")
return errors.New("xxx")
}
})
// 服务二: 基于gin框架的web服务
g.Go(func() error {
ginSvr := NewGinHttpServer("0.0.0.0", 8080, func(engine *gin.Engine) {
engine.GET("/hello", func(c *gin.Context) {
c.String(http.StatusOK, "Hello World!")
})
})
select {
// 监听context是否被取消, 取消则终止服务
case <-ctx.Done():
//停止服务操作
log.Println("收到context取消信号,停止服务二")
err := ginSvr.Stop()
if err != nil {
log.Printf("停止服务二出错: %v", err)
} else {
log.Println("成功停止服务二")
}
return ctx.Err()
// http服务
case err := <-ginSvr.Serve():
// 如果服务完成并未出错, errgroup不会对返回的context进行取消操作
if err != nil {
log.Printf("服务二出错: %v", err)
}
return err
}
})
// 等待所有服务完成,或者某个服务报错并终止所有服务
err := g.Wait()
log.Printf("程序退出:%v", err)
}
退出程序不应使用 kill -9
暴力杀死程序, 因为此动作触发的SIGKILL信号无法被处理
golang程序如何优雅地退出 https://blog.csdn.net/yzf279533105/article/details/117791707
Golang并发模型:并发协程的优雅退出 https://studygolang.com/articles/16646
Golang信号处理和优雅退出守护进程 https://studygolang.com/articles/10076
go程序优雅退出 https://jageros.github.io/note/docs/golang/grace-exit/