piaodazhu/gotcc是一个通用、简单而且好玩的并发控制器模块。它支持用户定义若干子任务,并且声明子任务之间存在的依赖关系,控制器将会在遵循依赖关系的前提下,并发执行这些子任务。
Github 求🌟: https://github.com/piaodazhu/gotcc
特性
一些有趣的特性:
- 根据用户声明的依赖关系,自动控制子任务的并发执行。
- 支持依赖逻辑表达式:与 /或 /非 /异或及其任意组合。比如任务 E 只有在 A 完成且 B 完成,且 C 或 D 不完成时才会启动,那么用这套逻辑表达式是可以直接定义的。
- 子任务之间多对多的结果传递。比如 C 依赖 A 和 B 的结果,那么 C 执行时的输入参数将会包括 A 和 B 各自的执行结果;如果 D 也依赖于 A ,那么 A 的结果也会传递给 D 一份。
- 支持对每一个任务都定义一个“撤销操作”,在异常时“回滚”。当任意子任务异常,所有执行中的任务将被取消,假设已经执行完成的任务序列为
D->A->B,那么随后undo(B)->undo(A)->undo(D)将执行。 - 收集控制器执行过程中产生的多个错误:哪些子任务出错了、返回的是什么 error 、哪些 undo 操作出错了、返回的是什么 error ,都会被收集。
个人感觉,和手动控制 goroutine 之间的同步比起来,这个控制器有如下优势:
- 让程序更加清晰。用这个模块,可以让用户功能代码和并发控制代码分离开。由于依赖是预先声明的,在分析代码时,自然而然可以根据依赖声明画出子任务的流图,比混杂的代码更容易分析。
- 具有一定的动态性。声明依赖逻辑表达式只关注任务的逻辑关系,而一个任务执行还是不执行,是在运行时决定的。任务如果有确定性的先后顺序还好,如果任务是动态的,那么手动控制这些 goroutine 的执行流程可能就得写很多 if 语句。随着子任务的增多,复杂性也会越来越高。
- 支持回滚,可以在子任务执行失败时尽可能恢复所有子任务执行前的状态。当然这取决于任务的性质和用户自己对 undo 操作的定义,控制器只是尽力逆序执行 undo 操作。如果手动控制,一旦发现某个 goroutine 出错了,回滚可能是一件比较麻烦的事。
这个控制器目前也有不足,因为还没写几天,只写了几个基本的 Test ,可能存在没发现的 bug 。一些地方用了闭包、channel,interface{},甚至用map[string]interface{}来放参数表(目前只能想到这样的方法了)。虽然还没有做基准测试,但是可想而知会有一些的性能损耗。应该还有很多可以改进的地方,大家有兴趣可以试试来提 PR 。
起因
做这个小项目的起因是另一个在我的任务中,需要在不同节点上执行不同的命令,这些命令有的具有先后顺序有的则可以并行,一旦有一个命令失败我希望能让所有已经执行过的命令回滚(比如 insmod 对应 rmmod ,启动进程对应杀死进程、git commit 对应 git reset )。因为这个任务的背景不是在 K8s 上,想想还挺麻烦的。另一方面,这种具有依赖、支持回滚的并发控制应该不局限于眼前这个场景,于是干脆抽象出来,写了这样一个控制器的项目,之后可以直接拿来用。