Go 编程: routine, 这个压箱底的库可以推荐一下了

好久没写 Go 编程的文章了,最近发现

有很多 clone。 这个写了快一年的项目当时的目的是为了做压力测试。

现在回看整个项目与代码都存在太多的冗余,所以做了一下重构。

重构之后的项目,全部代码也就 80 行左右。

主要功能其实都已经在 routine 中实现了, job 其实只是做了一个简单的组装而已。

所以,也就借着这次重构,向大家介绍一下,这个压箱底的库 routine.这个库自从写出来以后,基本上我所有的 Go 项目都会使用。

routine 做了什么

为什么要写这么一个基础库呢?

每段程序都是从 main 函数开始的,但是,却不会将整个程序的功能实现都放在 main 函数里。而是,通过层层功能的抽象与封装,最终,在 main 函数仅提供功能函数的入口。所以,当我们看很多大型程序时,其实,main 函数是非常简单的。

但是,即使 main 函数越变越简单,有些必要的功能则是逃不掉的。例如程序启动参数、程序的信号处理,这些通常还是会放在 main 函数进行处理。

除了以上 main 函数本身的处理以外,我发现将程序中的执行绪,也就是固定的 Go 协程的入口放在 main 函数中进行定义,可以帮助维护者更加快速的理解应用的逻辑实现。

所以,我就写了这样一个基础库,我只需要将具体的功能实现作为 Executor 接口的实现即可。使用起来就是这样:

package main

import (
	"log"
	"context"

	"github.com/x-mod/routine"
)

func main(){

    //功能实现
    YourExecutor := routine.Command("echo", routine.ARG("hello routine!"))

    //Main封装
	if err := routine.Main(
		context.TODO(),
        routine.Executor(YourExecutor),
        routine.Signal(syscall.SIGINT, routine.SigHandler(func() {
				os.Exit(1)
			})),
	); err != nil {
		log.Fatal(err)
	}
}

routine 协程控制

很多刚刚初级 Go 语言开发人员,常常在控制协程犯如下错误:

wg sync.WaitGroup

wg.Add(1) //! 正确写法
go func(){
    wg.Add(1) //! 错误写法
    defer wg.Done()
    ...
}()
wg.Wait()

提供一个简单协程控制的基础库,可以规避类似错误。

整个routine的协程逻辑可以通过下图进行一个简单的展示。

routine

routine 执行器

routine 库除了提供一个 main 函数的基础框架与协程控制以外,还提供了很多功能函数的执行器。包括:

  • 重试执行 retry
  • 重复执行 repeat
  • 计划执行 crontab
  • 并发执行 concurrent

等等,很多功能性封装。

import "github.com/x-mod/routine"

//timeout
timeout := routine.Timeout(time.Minute, exec)

//retry
retry := routine.Retry(3, exec)

//repeat
repeat := routine.Repeat(10, time.Second, exec)

//concurrent
concurrent := routine.Concurrent(4, exec)

//schedule executor
crontab := routine.Crontab("* * * * *", exec)

//command
command := routine.Command("echo", routine.ARG("hello routine!"))

//parallel
parallel := routine.Parallel(exec1, exec2, exec3, ...)

//sequence
sequece := routine.Append(exec1, exec2, exec3, ...)

routine 实现 job

所以,有了 routine 库以后,重构job的功能,就非常简单,真的仅仅只是简单的组装代码而已了。

不妨看一下, job的核心代码:

func Main(c *cmd.Command, args []string) error {
	if len(args) == 0 {
		return fmt.Errorf("job command required")
	}
	cmdOpts := []routine.CommandOpt{}
	for index, argument := range args {
		if index >= 1 {
			cmdOpts = append(cmdOpts, routine.ARG(argument))
		}
	}
	command := routine.Executor(routine.Command(args[0], cmdOpts...))
	if viper.GetDuration("cmd-timeout") > 0 {
		command = routine.Timeout(viper.GetDuration("cmd-timeout"), command)
	}
	if viper.GetInt("retry") > 0 {
		command = routine.Retry(viper.GetInt("retry"), command)
	}
	if viper.GetInt("repeat-times") > 0 {
		command = routine.Repeat(
			viper.GetInt("repeat-times"),
			viper.GetDuration("repeat-interval"),
			command)
	}
	if viper.GetInt("concurrent") > 0 {
		command = routine.Concurrent(viper.GetInt("concurrent"), command)
	}
	if viper.GetDuration("job-timeout") > 0 {
		command = routine.Timeout(viper.GetDuration("job-timeout"), command)
	}
	if len(viper.GetString("schedule")) > 0 {
		command = routine.Crontab(viper.GetString("schedule"), command)
	}
	ctx, cancel := context.WithCancel(context.TODO())
	return routine.Main(
		ctx,
		command,
		routine.Signal(syscall.SIGINT, routine.SigHandler(func() {
			cancel()
		})),
	)
}

当然,job 项目中还用到了,我封装的另外一个库 cmd,通过这个库可以快速的实现一个命令行程序。感兴趣的同学不妨参考一下。

阅读