goroutine 池的几种简易实现

95 阅读1分钟

协程池的目的,简单来说,限制goroutine数量来完成指定任务。

具体来说,比如:控制只用 5 个 groutine 来排队执行完 100 个任务。

我们再加一些要求:pool 是先启动动态执行的,我们可以动态加入任务,而不是预先设置好任务然后才能开始启动 pool 进行执行

思路一 使用有缓冲 channel 直接进行限制

func (p *Pool) Run() {
    limitChan := make(chan struct{}, p.limit)

    for task := range p.taskChan {
       // 利用channel的阻塞特性,来控制goroutine的数量
       limitChan <- struct{}{}
       go func(task *Task) {
          defer func() {
             <-limitChan
          }()
          task.f()
       }(task)

       fmt.Println("goroutine num: ", runtime.NumGoroutine())
    }
}

参考完整实现:goplay.tools/snippet/y2m…

思路二 直接开 worker 并行阻塞并行取任务

比较经典的实现,充分利用基本特性

我们知道 range 取 channel 是在关闭前一直阻塞的,那么我们开多个 worker 分别在他们的协程里取 任务执行,最后关闭统一停止就好了

func (p *Pool) Run() {
    for i := 0; i < p.wokerSize; i++ {
       p.wg.Add(1)
       go func() {
          defer p.wg.Done()
          for task := range p.taskChan {
             task.f()
          }
          fmt.Println("goroutine num: ", runtime.NumGoroutine())
       }()
    }
}

其他实现要点:

  • 先关闭 channel 再等待
  • 怎么确保任务都执行完的?主流程是无缓冲 channel ,会确保都被 worker 取到才走到 close