优雅退出设计步骤
在 Go 项目中,设计优雅退出(Graceful Shutdown)时,通常需要确保在收到退出信号时,程序能够安全地清理资源并优雅地退出。以下是常见的优雅退出设计步骤:
步骤 1:创建 context.Context
和 cancel
函数
- 使用
context.WithCancel()
创建一个取消上下文,用于传递取消信号。 cancel()
函数可以在收到退出信号时调用,从而通知所有正在运行的 goroutine 停止执行。
步骤 2:通过 context.WithCancel()
创建取消上下文
- 为确保优雅退出,
cancel()
函数需要在主线程的退出信号处理之前执行,保证所有协程能够及时响应取消信号。
步骤 3:处理所有协程的退出
- 启动多个协程并确保它们能够响应
ctx.Done()
信号,及时停止执行并清理资源。 - 使用
select
语句监听ctx.Done()
,以便协程能够在收到取消信号时退出。
步骤 4:触发 cancel()
并等待协程退出
- 在收到退出信号(如系统信号 SIGINT, SIGTERM 等)时,优先调用
cancel()
,以确保通知所有正在运行的协程退出。 - 然后等待所有协程退出,通常使用
sync.WaitGroup
来等待所有协程完成退出。
package mainimport ("context""fmt""os""os/signal""sync""syscall""time"
)func doWork(ctx context.Context, id int, wg *sync.WaitGroup) {defer wg.Done()for {select {case <-ctx.Done():// 当收到取消信号时,退出 goroutinefmt.Printf("goroutine %d: work cancelled\n", id)returndefault:// 模拟工作fmt.Printf("goroutine %d: working...\n", id)time.Sleep(1 * time.Second)}}
}func main() {// 创建一个取消上下文ctx, cancel := context.WithCancel(context.Background())defer cancel()var wg sync.WaitGroup// 启动多个 goroutinefor i := 0; i < 3; i++ {wg.Add(1)go doWork(ctx, i, &wg)}// 捕获终止信号signalChan := make(chan os.Signal, 1)// syscall.SIGINT: 终端执行 Ctrl + C// syscall.SIGTERM: 终端执行 kill $pid// syscall.SIGQUIT: 终端执行 Ctrl + \signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)// 等待退出信号select {case <-signalChan:// 在退出信号到达时,先执行 cancel,发出取消信号fmt.Println("Received termination signal. Cancelling context...")cancel()// 等待所有 goroutine 完成退出wg.Wait()fmt.Println("Graceful shutdown completed.")}
}