如何控制协程的执行顺序

212 阅读1分钟

github项目地址:

kamildrazkiewicz/go-flow

下面分析下代码里面是如何保证协程按顺序执行的

先介绍下设计思路,

  1. 每一个执行函数都接收一个map[string]interface{}为参数,然后返回一个interface{}, error
  2. 每一个函数都绑定一个有缓冲的channel,但是channel的缓冲区需要根据依赖关系来确定,初始值为1。 这里举例说明,如果A依赖{B,C,D}函数的执行,那么因为A->B的依赖关系,所以B的缓冲区大小需要+1,变成2,对于C,D也是同样的操作
  3. 函数执行前需要等待从它依赖的函数绑定的channel中读取一个数据
  4. 函数执行结束后,会把函数的返回值,输入到绑定的channel里面,所以依赖该函数执行的其他函数就可以正常向下执行
  5. 主协程会等待执行其他函数的所有协程结束,通过从每个函数对应的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的初始化只执行一次
}
  1. 具体的函数分析
// 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)
}