Golang 实现包的初始化控制器与流程控制器
背景
在go的工程中,有时init的顺序是至关重要的,本文写了一个小控制器去管理init的顺序,可以根据自己的要求设置不同的权重来实现加载顺序。
本文控制器主要实现两个功能,一是按照优先级加载包的引用,二是流程控制,主流程与异步流程存在顺序要求(实现类似sync.WaitGroup功能)
原理
优先级加载:设计一个优先级队列,将所有初始化函数(包括退出函数)注册到优先级队列中,在所有类注册完成之后统一按照优先级进行初始化。
流程控制:通过原子计数器进行异步数据的控制。
本片代码结合Ioc一起使用,有不理解可以查看 GO语言的控制反转 (IOC)在工程中应用
初始化控制
结构体定义
func DefaultApplicationLifeCycle() *ApplicationLifeCycle {
return appInit
}
type InitProc func() error
type InitializeDoneProc func(err error)
type ShutdownProc func(ctx context.Context) error
type initProcCtx struct {
name string
priority int
proc InitProc
}
type shutdownProcCtx struct {
name string
priority int
proc ShutdownProc
}
type ApplicationLifeCycle struct {
allProcs []initProcCtx // 需要初始化执行列表
allShutdown []shutdownProcCtx // 需要销毁执行列表
launched bool // 已加载标识
shutdownRequested bool // 已退出标识
served bool // 异步函数加载标识
once sync.Once
waitCounter int32
}
初始化控制
/*
根据优先级启动各对象的初始化函数
*/
func (alc *ApplicationLifeCycle) Launch() error {
if alc.launched {
return fmt.Errorf("already launched")
}
min := math.MaxInt32
max := math.MinInt32
for _, ctx := range alc.allProcs {
if ctx.priority > max {
max = ctx.priority
}
if ctx.priority < min {
min = ctx.priority
}
}
for i := min; i <= max; i++ {
for _, ctx := range alc.allProcs {
if ctx.priority == i {
fmt.Println("calling initializer for ", ctx.name)
err := ctx.proc()
if err != nil {
return fmt.Errorf("luanch application failed: %v", err)
}
}
}
}
alc.launched = true
return nil
}
/*
根据优先级执行各对象的初始化函数
*/
func (alc *ApplicationLifeCycle) Shutdown(ctx context.Context) {
alc.shutdownRequested = true
alc.once.Do(func() {
min := math.MaxInt32
max := math.MinInt32
for _, sctx := range alc.allShutdown {
if sctx.priority > max {
max = sctx.priority
}
if sctx.priority < min {
min = sctx.priority
}
}
for i := min; i <= max; i++ {
for _, sctx := range alc.allShutdown {
if sctx.priority == i {
fmt.Println("calling finalizer for ", sctx.name)
err := sctx.proc(ctx)
if err != nil {
fmt.Println("shutdown application failed: ", err)
}
}
}
}
fmt.Println("application shutdown")
})
}
/*
注册初始化函数,本质是一个优先级队列,可以通过优先级控制函数执行顺序,而不是根据根据包的引用顺序。
Launch函数执行
*/
func (alc *ApplicationLifeCycle) RegisterInitializer(name string, proc InitProc, priority int) error {
if alc.launched {
return fmt.Errorf("too late to register intializer, application already launched")
}
alc.allProcs = append(alc.allProcs, initProcCtx{name, priority, proc})
return nil
}
/*
注册退出清理函数,Shutdown执行
*/
func (alc *ApplicationLifeCycle) RegisterFinalizer(name string, proc ShutdownProc, priority int) error {
if alc.shutdownRequested {
return fmt.Errorf("too late to register finalizer")
}
alc.allShutdown = append(alc.allShutdown, shutdownProcCtx{
name: name,
priority: priority,
proc: proc,
})
return nil
}
使用
注册
// module.go
func init() {
app.DefaultApplicationLifeCycle().RegisterInitializer("module", func() error {
mo := &ModuleObj{}
// static assert 静态断言类型检测
func(t app.Module) {}(mo)
app.GetOrCreateRootContainer().RegisterTo(mo, (*app.Module)(nil), ioc.Singleton)
app.GetOrCreateRootContainer().Invoke(func(r app.Resource) {
rs = r
})
return nil
}, 2)
app.DefaultApplicationLifeCycle().RegisterFinalizer("module", func(ctx context.Context) error {
fmt.Println("module exit")
return nil
}, 2)
}
//resource.go
func init() {
app.DefaultApplicationLifeCycle().RegisterInitializer("resource", func() error {
mo := &ResourceObj{name: "mongo"}
// static assert 静态断言类型检测
func(t app.Resource) {}(mo)
//rd := &ResourceObj{name: "redis"}
app.GetOrCreateRootContainer().RegisterTo(mo, (*app.Resource)(nil), ioc.Singleton)
//app.GetOrCreateRootContainer().RegisterTo(rd, (*app.Resource)(nil), ioc.Singleton)
return nil
}, 1)
app.DefaultApplicationLifeCycle().RegisterFinalizer("resource", func(ctx context.Context) error {
fmt.Println("resource exit")
return nil
}, 1)
}
//service.init() 略
使用
err := app.DefaultApplicationLifeCycle().Launch()
if err != nil {
fmt.Println("DefaultApplicationLifeCycle Launch err ", err)
return
}
var s1 app.Service1
app.GetOrCreateRootContainer().Invoke(func(service app.Service1) {
s1 = service
})
s1.AddData("IOC Test")
app.DefaultApplicationLifeCycle().Shutdown(context.Background())
测试
流程控制
自实现的WaitGroup功能
/*
用于流程控制,所有异步的初始化完成之后,这里才会跳出循环,利用原子计数器实现的一个自旋锁
*/
func (alc *ApplicationLifeCycle) WaitUntilInitialized(ctx context.Context) {
alc.served = true
// alc.serveWaitGroup.Wait()
for {
select {
case <-ctx.Done():
return
default:
if alc.waitCounter == 0 {
return
}
time.Sleep(time.Millisecond * 50) // sleep 50ms
}
}
}
/*
用于流程控制 原子计数器+1,与-1的处理闭包函数
*/
func (alc *ApplicationLifeCycle) RegisterInitializeAwait(name string) (InitializeDoneProc, error) {
if alc.served {
return nil, fmt.Errorf("too late to register intialize await")
}
once := sync.Once{}
atomic.AddInt32(&alc.waitCounter, 1)
fmt.Println("register intialize await for ", name)
return func(err error) {
once.Do(func() {
if err != nil {
fmt.Println(fmt.Sprintf("initialize await `%s` report a failure: %v", name, err))
} else {
fmt.Println(fmt.Sprintf("`%s` initialized", name))
}
atomic.AddInt32(&alc.waitCounter, -1)
})
}, nil
}
应用
Service2 的接口定义与实现
type Service2 interface {
AddData(string)
DelData(string)
SyncData(t int)
InitService(done InitializeDoneProc)
}
type Service2 struct {
initDone app.InitializeDoneProc
}
func (s2 *Service2) InitService(done app.InitializeDoneProc) {
s2.initDone = done
}
func (s2 *Service2) AddData(str string) {
fmt.Println("Service2 AddData ", str)
module.DataToSave(str)
}
func (s2 *Service2) DelData(str string) {
fmt.Println("Service2 DelData ", str)
module.DataToRemove(str)
}
func (s2 *Service2) SyncData(t int) {
time.Sleep(time.Second * 2 * time.Duration(t))
fmt.Println("SyncData over : ", t)
if s2.initDone != nil {
s2.initDone(nil)
}
}
使用
//模拟不同场景的异步处理。
for i := 0; i < 5; i++ {
var s2 app.Service2
app.GetOrCreateRootContainer().Invoke(func(service app.Service2) {
s2 = service
initDone, _ := app.DefaultApplicationLifeCycle().RegisterInitializeAwait(fmt.Sprintf("service: %d", i))
s2.InitService(initDone)
})
s2.SyncData(i)
}
app.DefaultApplicationLifeCycle().WaitUntilInitialized(context.Background()) //会进行阻塞
fmt.Println("App Wait Over")
测试
思考
这里提供一个解决这两种问题的思路,每个人可能都有自己不同的处理方式,可以通过显示Init调用,也可以使用对象中添加 WaitGroup实现。只要能更好的集成到服务框架,能更少的产生理解偏差,更容易使用,耦合度更低就是好程序。