公众号原文地址:如何设计一个好的协程池?
对于go语言来说,go语言在调度Goroutine已经优化的非常不错了,而且go官方曾说go开上千万个协程没啥大问题。那为什么在协程领域还需要弄一个协程池出来呢?
我们知道,池化思想本质就是复用资源,协程虽然很轻量级,但是如果无休止的开辟Goroutine依然会出现高频率地调度Groutine,就会浪费很多上下文切换的资源。
举个例子,比如游戏要在1000个区开10个活动,就有1w个任务,按照go的实现方式,可以同时开1w个协程去处理。但是我们知道,开一个协程大概需要用到2k左右的内存,1W个任务就是20M左右,同时数万个任务,真正同时执行的任务数受限于机器的CPU,没执行的任务就只能放在P的本地队列和全局队列中。
下图展示了未使用协程池和使用协程池的效果:
从图中我们可以看到,使用协程池后,常驻的协程数远小于未使用协程池的协程数。
利用池化思想将协程进行复用,通过给少数协程不断加任务方式,降低协程数。比如之前需要创建几万个协程来执行任务,通过协程池,可以降到只需要数百个协程,而协程本身的内存占用也从20M降到几百K。
下面我们来谈谈协程池的设计。
协程池本质是复用协程,我们需要将部分协程预分配放入池中。每次来新任务时,业务协程通过协程池取出空闲协程,然后将任务发给取出的空闲协程,空闲协程收到任务后执行任务,执行完任务后再放回协程池。此外,对于一个协程池来说,应该要有协程定时对协程池中的协程进行清理,防止协程长期空闲导致资源浪费。
协程池的原理图如下:
从图中可以看到,任务提交后会从协程池中取出一个空闲协程来执行当前的任务,空闲协程执行完后会放回协程池中;在图中右上角有个协程会定时清理协程池中闲置过久(过期)的协程。
从整个原理图中,我们可以看出,协程池通过协程复用来实现少量协程执行大量任务,从而能防止协程数量过多,浪费不必要的资源。
至于协程池的实现方案,现在比较常见的有:ants、fasthttp等等。本篇博客只讲协程池的技术原理,并不涉及实现,后续会考虑出ants的源码分析,根据源码来解读协程池的设计思想。
总结
- 协程池的本质就是复用协程。
- 协程池目的就是通过对协程的复用,防止协程过多,浪费不必要的资源。
- 协程池适用于需要大量协程执行任务的场景,如网络框架、爬虫等