启动服务之时先初始化一个 Goroutine Pool 池,这个Pool维护了一个类似栈的FILO队列 ,里面存放负责处理任务的Worker,然后在client端提交task到Pool中之后,在Pool内部,接收task之后的核心操作是:
- 检查当前Worker队列中是否有空闲的Worker,如果有,取出执行当前的task;
- 没有空闲Worker,判断当前在运行的Worker是否已超过该Pool的容量,是 — 阻塞等待直至有Worker被放回Pool;否 — 新开一个Worker(goroutine)处理;
- 每个Worker执行完任务之后,放回Pool的队列中等待。
调度过程如下:
按照这个设计思路,我实现了一个高性能的Goroutine Pool,较好地解决了上述的大规模调度和资源占用的问题,在执行速度和内存占用方面相较于原生goroutine并发占有明显的优势,尤其是内存占用,因为复用,所以规避了无脑启动大规模goroutine的弊端,可以节省大量的内存。
总结:
至此,一个高性能的 Goroutine Pool 开发就完成了,事实上,原理不难理解,总结起来就是一个『复用』,具体落实到代码细节就是锁同步、原子操作、channel通信等这些技巧的使用,ant
这整个项目没有借助任何第三方的库,用golang的标准库就完成了所有功能,因为本身golang的语言原生库已经足够优秀,很多时候开发golang项目的时候是可以保持轻量且高性能的,未必事事需要借助第三方库。
关于ants
的价值,其实前文也提及过了,ants
在大规模的异步&同步批量任务处理都有着明显的性能优势(特别是异步批量任务),而单机上百万上千万的同步批量任务处理现实意义不大,但是在异步批量任务处理方面有很大的应用价值,所以我个人觉得,Goroutine Pool真正的价值还是在:
- 限制并发的goroutine数量;
- 复用goroutine,减轻runtime调度压力,提升程序性能;
- 规避过多的goroutine侵占系统资源(CPU&内存)。