golang實現簡單的併發任務消費

liangxingwei發表於2018-07-01

又是一個golang func的簡單運用,不得不說這個語法糖真的很棒。

併發任務消費的本質其實就是多個自旋的goroutine來監聽多個func 型別的chan,當收到從chan傳遞過來的func後,就執行之。

核心程式碼

type Work func()// 重新定義func的型別

var workChan []chan Work// 接收工作的chan
var wg = &sync.WaitGroup{}

func Start(size int) {// size規定了自旋的goroutine的個數
	for i := 0; i < size; i++ {
		workChan[i] = make(chan Work)
		go process(i)
	}
}
func process(i int) {
	wg.Add(1)
	defer wg.Done()
	if works, ok := workChan[i]; ok {
		for {
			work, ok := <-works// 收到工作
			if ok {
				work()// 執行工作
			}
		}
	}
}
複製程式碼

通過定義一個slice型別的chan Work,使用多個goroutine來接收chan的工作,收到工作便執行。同時使用sync.WaitGroup來保證goroutine的無限自旋。

新增工作

外部傳送工作其實就是往chan內傳送工作的邏輯,考慮到為外部提供更為良好的使用,於是簡單封裝一下,如下

type Work func()

type workerGroup struct {
	workChan map[int]chan Work
	*sync.WaitGroup
	isStop bool
}

func NewWorkerGroup() *workerGroup {
	wg := &workerGroup{WaitGroup: &sync.WaitGroup{}}
	wg.workChan = make(map[int]chan Work)
	return wg
}
// 傳送任務
func (w *workerGroup) SendWork(work Work, i int) error {
	if !w.isStop {
		if works, ok := w.workChan[i]; ok {
			works <- work
			return nil
		} else {
			return errors.New("error i")
		}
	}else{
		return errors.New("the worker group has been closed")
	}
}
func (w *workerGroup) process(i int) {
	w.Add(1)
	defer w.Done()
	if works, ok := w.workChan[i]; ok {
		for {
			work, ok := <-works
			if ok {
				work()
			}
		}
	}
}
func (w *workerGroup) Start(size int) {
	for i := 0; i < size; i++ {
		w.workChan[i] = make(chan Work)
		go w.process(i)
	}
}
// 監聽退出
func (w *workerGroup) OnStop() {
	go func() {
		for {
			sig := make(chan os.Signal, 1)
			signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
			select {
			case <-sig:
				w.isStop = true
				func() {
					for _, works := range w.workChan {
						if len(works) <=0 {
							close(works)
						}
					}
				}()
			}
			break
		}
		log.Println("worker group succeed to close")
		os.Exit(1)
	}()
	w.Wait()
}
複製程式碼

外部程式碼需要使用到這個模組的時候,只需要建立工作組,開啟一下,呼叫sendwork來傳送工作即可,如下

func main() {
	wg := NewWorkerGroup()
	wg.Start(6)
	tick := time.Tick(time.Second)
	wg.OnStop()
	wg.SendWork(func() {
		fmt.Println("work")
	}, 5)
}
複製程式碼

總結

以上程式碼只是簡單的使用,並沒有考慮很多的異常情況,比如在SendWork函式中,若是chan裡面已經存在值了,那麼sendwork就會被阻塞;其次,若是系統退出時,goroutine中存在未完成的工作,也會一併退出;此外,若是工作具有返回值,需要通知結果,是直接逐層返回結果還是用非同步通知回撥函式的方式,都有待考量。還有更多的問題,歡迎拍磚指正。

相關文章