协程池的目的,简单来说,限制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