之前介绍过一个多协程的Parallelize,允许多个协程并发执行任务的函数,今天手写一个控制能力更强的的ruuner,初步实现单个协程处理,后续将继续改进为多协程处理:
package runner
import (
"errors"
"os"
"os/signal"
"time"
)
type Runner struct {
// interrupt channel reports a signal from the
// operating system.
interrupt chan os.Signal
// complete channel reports that processing is done.
complete chan error
// timeout reports that time has run out.
timeout <-chan time.Time
// tasks holds a set of functions that are executed
// synchronously in index order.
tasks []func(int)
}
// ErrTimeout is returned when a value is received on the timeout.
var ErrTimeout = errors.New("received timeout")
// ErrInterrupt is returned when an event from the OS is received.
var ErrInterrupt = errors.New("received interrupt")
// New returns a new ready-to-use Runner.
func New(d time.Duration) *Runner {
return &Runner{
interrupt: make(chan os.Signal, 1),complete: make(chan error),timeout: time.After(d),}
}
// Add attaches tasks to the Runner. A task is a function that
// takes an int ID.
func (r *Runner) Add(tasks ...func(int)) {
r.tasks = append(r.tasks,tasks...)
}
// Start runs all tasks and monitors channel events.
func (r *Runner) Start() error {
// We want to receive all interrupt based signals.
// Run the different tasks on a different goroutine.
go func() {
r.complete <- r.run()
}()
select {
// Signaled when processing is done.
case err := <-r.complete:
return err
// Signaled when we run out of time.
case <-r.timeout:
return ErrTimeout
}
}
// run executes each registered task.
func (r *Runner) run() error {
for id,task := range r.tasks {
// Check for an interrupt signal from the OS.
if r.gotInterrupt() {
return ErrInterrupt
}
// Execute the registered task.
task(id)
}
return nil
}
// gotInterrupt verifies if the interrupt signal has been issued.
func (r *Runner) gotInterrupt() bool {
select {
// Signaled when an interrupt event is sent.
case <-r.interrupt:
// Stop receiving any further signals.
signal.Stop(r.interrupt)
return true
// Continue running as normal.
default:
return false
}
}
注解写的很详细,我简单介绍一下;Runner是一单协程的运行器,里面几个属性interrupt获取os的信号量,complete返回执行结果,timeout设置超时时间,如果超时结束运行,tasks是报错任务的。添加任务通过Add方法:将方法加入到切片中,Start方法启动任务,这里只启动一个协程,后期改进,run方式是具体执行,执行task函数gotInterrupt获取os的消息。
怎样使用呢?看下面:
const timeout = 2*time.Second
func main() {
r := runner.New(timeout)
r.Add(crateTask(),crateTask(),crateTask())
if err := r.Start(); err != nil {
switch err {
case runner.ErrTimeout:
fmt.Println("timeout error")
os.Exit(1)
case runner.ErrInterrupt:
fmt.Println("interrupt error")
os.Exit(2)
}
}
log.Println("end !!!!")
}
func crateTask()func(int) {
return func(id int) {
fmt.Println("exec id :",id)
time.Sleep(time.Duration(id)*time.Second)
}
}
通过改变timeout时间可以准确的控制任务执行时间,上面2秒的例子,保证每个任务都能运行,执行结果如下: exec id : 0 exec id : 1 exec id : 2 timeout error exit status 1 如果改成5s当然能保证执行完成任务: exec id : 0 exec id : 1 exec id : 2 2017/04/11 09:59:12 end !!!!