并发模型,runner
并发模型,runner
runner
模型概述
runner模型做的事情就是,我们把任务丢给一个runner,然后runner去帮我们在后台跑这些任务,如果执行任务的过程超时或者收到了中断,我们就报错。
runner的定义
这里我们定义了一个结构体Runner和两种error
var (
ErrTimeout = errors.New("connot finish tasks within the timeout") //超时error
ErrInterrupt = errors.New("received interrupt from OS") //中断error
)
type Runner struct {
interrupt chan os.Signal //存放中断信号
complete chan error //放错误
timeout <-chan time.Time //超时信号
tasks []func(int)
}
func New(t time.Duration) *Runner {
return &Runner{
interrupt: make(chan os.Signal, 1),
complete: make(chan error),
timeout: time.After(t),
tasks: make([]func(int), 0),
}
}
这里的New函数里,赋给timeout的是一个特殊的单向通道,time.After(t)会在一时间t后返回一个time.Time类型的单向通道,所以我们就通过这个东西来实现计时。
添加任务
func (r *Runner) AddTasks(tasks ...func(int)) {
r.tasks = append(r.tasks, tasks...)
}
这里的实现很简单,指类的任务是func(int)类型的函数,将函数放到runner的tasks切片中去。
执行任务
func (r *Runner) run() error {
for id, task := range r.tasks {
select {
case <-r.interrupt:
signal.Stop(r.interrupt)
return ErrInterrupt
default:
task(id)
}
}
return nil
}
func (r *Runner) Start() error {
signal.Notify(r.interrupt, os.Interrupt) //这里指定os.Interrupt类型的signals会被送进r.interrupt这个通道
go func() {
r.complete <- r.run()
}()
select {
case err := <-r.complete:
return err
case <-r.timeout:
return ErrTimeout
}
}
Start函数开头的signal.Notify(r.interrupt, os.Interrupt) 的作用是指定os.Interrupt这个类型的signal会被送进r.interrupt这个通道。
后续起一个协程去执行r.run,run会返回error类型到r.complete,所以我们使用select 结合通道,有run中返回的error或者timeout,就返回,如果运行中收到中断,例如ctrl+c,就会停掉
测试一下
func task(id int) {
time.Sleep(1 * time.Second)
}
func main() {
runner := runner.New(5 * time.Second)
runner.AddTasks(task,task,task,task)
err := runner.Start()
if err != nil {
fmt.Println(err.Error())
}else {
fmt.Println("完成")
}
}
执行期间,输入一个ctrl+c,显示如下内容
增加几个task后,会出现这个
其实这个runner中通过time.After()进行计时并不严谨,因为这个计时是从runner对象被创建出来开始的,而不是从开始运行开始的。