说起 Go 协程, 如果你了解 Go 调度器以及其底层实现 G/P/M 模型,当然有助于对协程进行控制。
简单的协程控制,很多人都会使用 sync.WaitGroup
进行多协程的控制。但是 sync.WaitGroup
面对图示的调度控制就显得无能为力了。
但需要在应用开发中实现类似上图的精准调度,就需要借助其它手段了。
上图的协程调度看似复杂,如果仔细分析一下,就会发现单个协程的启动需要等待依赖协程完成才能开始。所以,问题的关键是实现协程间的前置依赖。
那么,这样一个前置依赖该如何实现呢?这里直接贴出我的方案,你也可以想想你要如何实现。
package event
import (
"sync"
"sync/atomic"
)
type Event struct {
fired int32
c chan struct{}
once sync.Once
}
func New() *Event {
return &Event{c: make(chan struct{})}
}
func (ev *Event) Fire() int32 {
atomic.AddInt32(&ev.fired, 1)
ev.once.Do(func() {
close(ev.c)
})
return ev.fired
}
func (ev *Event) Done() <-chan struct{} {
return ev.c
}
func (ev *Event) HasFired() bool {
return atomic.LoadInt32(&ev.fired) > 0
}
这样一个简单的事件触发器就可以来模拟协程前置依赖。例如,图示中的B1
的启动依赖于A1
与C1
,那么我们的实现代码就可以这样实现:
package main
import (
"github.com/x-mod/event" //Event 开源项目在这里
)
func main(){
//前置依赖
a1 := event.New()
b1 := event.New()
c1 := event.New()
//A1 协程
go func(){
defer a1.Fire()
// A1 LOGIC ...
}
//B1 协程
go func(){
defer b1.Fire()
// 等待前置依赖完成
<-a1.Done()
<-c1.Done()
// B1 LOGIC ...
}
//C1 协程
go func(){
defer c1.Fire()
// C1 LOGIC ...
}
}