设计思路
- Go语言特性:
- 使用
context.Context
来控制goroutine的生命周期。context
可以传递取消信号,各个模块的goroutine可以监听这个信号,在接收到取消信号时进行清理工作。
- 利用
sync.WaitGroup
来等待所有goroutine完成清理工作。
- 数据结构:
- 可以使用一个
map
来存储各个模块的关闭函数。例如 map[string]func() error
,其中键为模块名称,值为对应的关闭函数。这样可以方便地按照一定顺序调用各个模块的关闭函数。
- 同步机制:
- 对于每个模块的goroutine池,在关闭时需要保证所有正在运行的goroutine都能安全结束。可以使用
sync.Mutex
来保护对goroutine池状态的修改,同时结合 context.Context
来通知goroutine停止工作。
- 使用
sync.WaitGroup
等待所有模块的关闭函数执行完毕,确保整个系统安全关闭。
概念性代码框架
package main
import (
"context"
"fmt"
"sync"
)
// 定义模块关闭函数类型
type ShutdownFunc func() error
// 存储各个模块的关闭函数
var shutdownFuncs = make(map[string]ShutdownFunc)
// 注册模块关闭函数
func RegisterShutdownFunc(moduleName string, f ShutdownFunc) {
shutdownFuncs[moduleName] = f
}
// 全局优雅关闭函数
func GracefulShutdown(ctx context.Context) error {
var wg sync.WaitGroup
var errChan = make(chan error, len(shutdownFuncs))
// 按照一定顺序调用各个模块的关闭函数
for moduleName, shutdownFunc := range shutdownFuncs {
wg.Add(1)
go func(name string, f ShutdownFunc) {
defer wg.Done()
err := f()
if err != nil {
errChan <- fmt.Errorf("module %s shutdown error: %v", name, err)
}
}(moduleName, shutdownFunc)
}
go func() {
wg.Wait()
close(errChan)
}()
// 等待所有关闭函数执行完毕,或者上下文取消
for i := 0; i < len(shutdownFuncs); i++ {
select {
case err := <-errChan:
if err != nil {
return err
}
case <-ctx.Done():
return ctx.Err()
}
}
return nil
}
// 示例模块关闭函数
func Module1Shutdown() error {
// 清理Module1的资源
fmt.Println("Module1 is shutting down")
return nil
}
func Module2Shutdown() error {
// 清理Module2的资源
fmt.Println("Module2 is shutting down")
return nil
}
func main() {
// 注册模块关闭函数
RegisterShutdownFunc("Module1", Module1Shutdown)
RegisterShutdownFunc("Module2", Module2Shutdown)
ctx, cancel := context.WithCancel(context.Background())
// 模拟系统接收到关闭信号
go func() {
// 模拟一些业务逻辑
fmt.Println("System is running")
// 这里假设一段时间后接收到关闭信号
cancel()
}()
err := GracefulShutdown(ctx)
if err != nil {
fmt.Printf("Shutdown error: %v\n", err)
} else {
fmt.Println("System shutdown gracefully")
}
}