Go 如何优雅退出进程
优雅退出设计步骤
在 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 main
import (
"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():
// 当收到取消信号时,退出 goroutine
fmt.Printf("goroutine %d: work cancelled\n", id)
return
default:
// 模拟工作
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
// 启动多个 goroutine
for 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.")
}
}