github项目地址:
下面分析下代码里面是如何保证协程按顺序执行的
先介绍下设计思路,
- 每一个执行函数都接收一个map[string]interface{}为参数,然后返回一个interface{}, error
- 每一个函数都绑定一个有缓冲的channel,但是channel的缓冲区需要根据依赖关系来确定,初始值为1。 这里举例说明,如果A依赖{B,C,D}函数的执行,那么因为A->B的依赖关系,所以B的缓冲区大小需要+1,变成2,对于C,D也是同样的操作
- 函数执行前需要等待从它依赖的函数绑定的channel中读取一个数据
- 函数执行结束后,会把函数的返回值,输入到绑定的channel里面,所以依赖该函数执行的其他函数就可以正常向下执行
- 主协程会等待执行其他函数的所有协程结束,通过从每个函数对应的channel读取元素
- 顶部的数据结构
type Flow struct {
//流程图中的函数,键为函数的名称
funcs map[string]*flowStruct
}
//flowFunc 接受一个map[string]interface{}的参数,返回结果为interface{}
type flowFunc func(res map[string]interface{}) (interface{}, error)
type flowStruct struct {
Deps []string //flowStruct 依赖的其他函数的名称
Ctr int //C的缓冲区大小
Fn flowFunc // 协程需要执行的函数
C chan interface{} //chan
once sync.Once //控制channel的初始化只执行一次
}
- 具体的函数分析
// Done函数根据Ctr的值,向channel中填充数据
func (fs *flowStruct) Done(r interface{}) {
for i := 0; i < fs.Ctr; i++ {
fs.C <- r
}
}
//保证channel的关闭只执行一次
func (fs *flowStruct) Close() {
fs.once.Do(func() {
close(fs.C)
})
}
func (fs *flowStruct) Init() {
//初始化C是一个缓冲区大小为Ctr的channel
fs.C = make(chan interface{}, fs.Ctr)
}
//创建一个Flow实例
func New() *Flow {
return &Flow{
funcs: make(map[string]*flowStruct),
}
}
// 添加依赖关系, name需要等待d中的每一个元素对应的flowStruct中的Fn执行后才能执行
func (flw *Flow) Add(name string, d []string, fn flowFunc) *Flow {
flw.funcs[name] = &flowStruct{
Deps: d,
Fn: fn,
Ctr: 1, // 主协程需要等待所有的协程结束,所以这里需要为1
}
return flw
}
func (flw *Flow) Do() (map[string]interface{}, error) {
//flw.funcs是一个map, name为字符串funcs为结构体
for name, fn := range flw.funcs {
for _, dep := range fn.Deps {
//dep表示结构体中的Deps中的一个字符串
// prevent self depends
if dep == name {
return nil, fmt.Errorf("Error: Function "%s" depends of it self!", name)
}
// prevent no existing dependencies
if _, exists := flw.funcs[dep]; exists == false {
return nil, fmt.Errorf("Error: Function "%s" not exists!", dep)
}
//如果A依赖B函数,那么B函数所在的flowStruct中的channel容量需要增加1
flw.funcs[dep].Ctr++
}
}
return flw.do()
}
func (flw *Flow) do() (map[string]interface{}, error) {
var err error
res := make(map[string]interface{}, len(flw.funcs))
for _, f := range flw.funcs{
//Ctr已经由Do函数提前计算了
//初始化f中的f.C 的缓冲区大小为f.Ctr
f.Init()
}
for name, f := range flw.funcs {
go func(name string, fs *flowStruct) {
//延迟调用fs.Close方法,关闭fs 中的channel
defer func() { fs.Close() }()
results := make(map[string]interface{}, len(fs.Deps))
// drain dependency results
for _, dep := range fs.Deps {
//从当前结构体依赖的所有结构体中的channel中读取一个元素
results[dep] = <-flw.funcs[dep].C
}
//Fn接收一个map[string]interface对象,然后返回interface{} 和error
//Fn从上游的协程接收数据,然后进行处理
r, fnErr := fs.Fn(results)
if fnErr != nil {
// close all channels
for _, fn := range flw.funcs {
fn.Close()
}
err = fnErr
return
}
// exit if error
if err != nil {
return
}
//给下游提供数据
fs.Done(r)
}(name, f)
}
// wait for all
//所有函数的fs.C 中读取到一个数据
//因为fs.Ctr初始化为1,如果defer没执行,最后主协程也会读到数据,close之后,主协程也会得到数据
for name, fs := range flw.funcs {
res[name] = <-fs.C
}
return res, err
}
- 具体实例分析
package main
import (
"fmt"
"github.com/kamildrazkiewicz/go-flow"
"time"
)
func main() {
f1 := func(r map[string]interface{}) (interface{}, error) {
fmt.Println("function1 started")
time.Sleep(time.Millisecond * 1000)
return 1, nil
}
f2 := func(r map[string]interface{}) (interface{}, error) {
time.Sleep(time.Millisecond * 1000)
fmt.Println("function2 started", r["f1"])
return "some results", nil
}
f3 := func(r map[string]interface{}) (interface{}, error) {
fmt.Println("function3 started", r["f1"])
return nil, nil
}
f4 := func(r map[string]interface{}) (interface{}, error) {
fmt.Println("function4 started", r)
return nil, nil
}
//这里f1不依赖任何函数,f2依赖于f1,f3依赖于f1, f4依赖于f1
//所以f1的flowStruct的Ctr大小为3,f2的Ctr为2, f3,为2,f4为1
//f1不依赖任何函数,先执行,然后把返回值添加到channel中,所以f2,f3可以继续执行,然后f4
//可以往后执行
/**
*
function1 started
function3 started 1
function2 started 1
function4 started map[f2:some results f3:<nil>]
map[f1:1 f2:some results f3:<nil> f4:<nil>] <nil>
*/
res, err := goflow.New().
Add("f1", nil, f1).
Add("f2", []string{"f1"}, f2).
Add("f3", []string{"f1"}, f3).
Add("f4", []string{"f2", "f3"}, f4).
Do()
fmt.Println(res, err)
}