Golang 协程池 gopool 源码分析

写在前面

字节跳动开源项目gopool是一个十分轻量且高性能的协程池,如果在业务上没有什么特殊需求,它可以满足大部分日常开发中对于协程池的使用需求。本次我们就来一次研究一下gopool的实现原理。

总体架构

gopool的构成非常简单,由pool/worker/task三个对象协作完成协程池的工作。

  • pool:pool是协程池的一个完整实现,它通过 Pool interface 对用户暴露协程池操作方法。并且,管理着task和worker两个对象集合。
  • task:task是对协程池中的每个任务函数的封装,它通过自己的成员变量 f 来注册用户的待执行函数。同时,通过成员变量 next 来指向下一个待执行 task。pool和worker就是通过 task 构建的链表,来完成 task 的注册和task的取出执行。
  • worker:worker 是 task 的执行者,它在被启动(pool 去调用 worker 对象的 run 方法)后,会持续的从 task 链表中取出 task 对象,并执行其中的函数。一个 worker 一次只能执行一个 task,因此,worker的数量就可以代表当前 pool 中运行的任务总数。

使用gopool

gopool 的使用非常简单,只需要按如下方式将任务函数传入 pool 中,pool会自动创建 task 并管理 worker 去启动任务。因为 worker 在启动时是在一个 Goroutine 中去不断的取出 task 并执行之。因此,如下的调用方式在用户看来与直接通过 go func() 去执行一个 Goroutine 并没有什么区别。

go 复制代码
gopool.Go(func(){  
/// do your job  
})  

内部实现

pool 的初始化

gopool包在被导入时,会在 init 方法中调用 NewPool 方法完成 defaultPool 全局协程池的初始化。 NewPool 会返回前文所说的 pool 对象,pool对象就是 Pool interface 的实现类型。

后续用户在执行诸如 Go 和 CtxGo 方法去注册任务时,其实就是通过 defaultPool 去调用 pool 对象的 CtxGo 方法。 Go 和 CtxGo 的区别是, CtxGo 会接收调用者传入的 context 上下文,以便调用者通过 context 向执行函数 f 传入参数或者随时停止执行函数 f。Go 方法就是直接调用 CtxGo 方法,区别只是在 Go 方法中会向 CtxGo 传递一个 context.Background。

task的注册

现在我们从 CtxGo 方法开始,看看 pool 怎么去注册 task 和启动 worker。 在 CtxGo 方法中,首先会执行 taskPool.Get 方法,取出一个 task。将这个 task 注册到 pool 对象的 task 链表的尾部。

taskPool 是一个 sync.Pool 的变量。使用 sync.Pool 去管理 task 对象的原因是,在高并发场景下,每次调用 CtxGo 都必须生成 task 对象。而 sync.Pool 会去管理并复用这些 task,这样一来,就很大程度上减少了 CPU(包括内存分配和GC)对 task 对象的管理开销。 同样的,由于 worker 是用来执行 task 的对象,也需要频繁的生成/销毁,因此gopool中同样也使用 sync.Pool 对worker进行管理。

在完成 task 的注册后,pool 会判断以下两个条件是否有一个为真:

  1. pool 中 task 的数量超过了启动阈值,并且 worker 的数量小于 pool 中的并发任务数量限制。

这个启动阈值会在 NewPool 方法中通过调用 NewConfig 来默认配置

  1. pool 中没有正在执行的 worker。 只要以上两个判断有一个为真,那么就会通过 workerPool.Get()方法,从 sync.Pool 中取出一个 worker,通过 worke 的 run 方法,来启动 worker。

task的执行

worker 在 run 方法中会启动一个 Goroutinue,在这个 Goroutinue 中,主要进行两项工作:

  1. 不断的循环获取 task 任务对象,并调用 t.f() 来执行 task 中注册的任务函数,在task执行完成后,通过 Recycle 来向 taskPool 对象中归还已经执行完成的 task。
  2. 如果 pool 的 task链表为空,则关闭 worker。

总结

总得来说,gopool 的实现相当的轻量,在实现简单的 worker 任务并发控制的基础上,通过 sync.Pool 对性能做了优化,其对 sync.Pool 的使用很具有参考意义。无论是今天提到的gopool协程池,还是其他领域的高性能框架,所谓高性能的本质,就是通过技术手段,尽量减少 CPU 执行与核心逻辑无关或者会造成核心逻辑阻塞的操作。

相关推荐
qq_174482857516 分钟前
springboot基于微信小程序的旧衣回收系统的设计与实现
spring boot·后端·微信小程序
锅包肉的九珍1 小时前
Scala的Array数组
开发语言·后端·scala
心仪悦悦1 小时前
Scala的Array(2)
开发语言·后端·scala
2401_882727571 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架
心仪悦悦2 小时前
Scala中的集合复习(1)
开发语言·后端·scala
代码小鑫2 小时前
A043-基于Spring Boot的秒杀系统设计与实现
java·开发语言·数据库·spring boot·后端·spring·毕业设计
真心喜欢你吖2 小时前
SpringBoot与MongoDB深度整合及应用案例
java·spring boot·后端·mongodb·spring
激流丶2 小时前
【Kafka 实战】Kafka 如何保证消息的顺序性?
java·后端·kafka
uzong3 小时前
一个 IDEA 老鸟的 DEBUG 私货之多线程调试
java·后端
飞升不如收破烂~4 小时前
Spring boot常用注解和作用
java·spring boot·后端