用go语言实现一个有界协程池

写在文章开头

本篇文章算是对go语言系列的一个收尾,通过go语言实现一个实现一个简单的有界协程池。

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 "加群" 即可和笔者和笔者的朋友们进行深入交流。

详解go语言协程池的实现

整体交互流程设计

我们希望创建一个协程池,该协程池大小由用户决定,主协程不断生产任务并投递到channel中,协程池收到任务后,如果发现没有对应处理的协程worker则创建一个协程并处理传入的任务,反之这些任务就会有序得等待协程有序调度执行:

定义worker

基于上图我们给出worker的接口定义,按照我们的实现每一个任务都是一个worker,协程池的协程可以从channel中得到对应的Worker并执行其Task方法:

go 复制代码
type Worker interface {
 Task()
}

声明协程池

基于worker我们封装一个worker池,也就是本文提到的协程池,可以看到该Pool有一个worker的通道用于存放主协程投递进来的任务,而wg则用于控制协程的生命周期,这一点我们会在后续的工作代码中详尽说明:

go 复制代码
type Pool struct {
 //记录主协程投递的任务
 work chan Worker
 //控制工作协程的生命周期
 wg   sync.WaitGroup
}

创建协程池

有了协程池的定义之后,我们就可以编写协程池的,可以看到我们可以通过入参决定channel和协程的大小,通过传入maxGoroutines 设置wg的大小,当协程都没有任务执行时,才会调用wgDone方法,确保所有任务执行完成后,主协程才能退出:

go 复制代码
func New(maxGoroutines int) *Pool {
 //创建指定协程数的channel
 p := Pool{
  work: make(chan Worker, maxGoroutines),
 }

 //基于协程数创建倒计时门闩
 p.wg.Add(maxGoroutines)

 //创建maxGoroutines个协程获取channel的任务执行
 for i := 0; i < maxGoroutines; i++ {

  go func() {
   for w := range p.work {
    w.DoTask()

   }
   //任务执行完成且channel关闭之后,按下倒计时门闩
   p.wg.Done()
  }()
 }
 
 //返回pool的指针
 return &p
}

投递任务

当我们需要投递任务时,就可以将自实现的worker投递到channle中:

scss 复制代码
func (p *Pool) Run(w Worker) {
 //将任务w投递到channel中
 p.work <- w
}

关闭协程池

最后我们给出关于协程池关闭的实现,其逻辑比较简单:

  1. 关闭channel不再接受新任务。
  2. 调用waitGroupWait方法等待所有协程执行完再返回。
scss 复制代码
func (p *Pool) ShutDown() {
 close(p.work)
 p.wg.Wait()
}

测试代码

最后我们给出本文的测试代码,使用示例比较简单:

  1. 定义一个姓名切片,作为测试数据。
  2. 创建一个名为namePrinter 的结构体,内部包含name属性,该结构体会继承Worker实现打印姓名的Task方法。
  3. 创建一个channel和协程大小都为2的Pool
  4. 通过多协程循环遍历name切片并将其封装成namePrinter投递到chanel中。
  5. 协程池的协程消费这些打印姓名的任务。
  6. 调用shutDown方法等待协程池内部协程工作完成后退出主协程。
go 复制代码
// 创建一个测试用的姓名切片
var names = []string{
 "user.go-1",
 "user.go-2",
 "user.go-3",
 "user.go-4",
 "user.go-5",
}

// 实现worker接口 打印姓名
type namePrinter struct {
 name string
}

func (n *namePrinter) Task() {
 fmt.Println(n.name)
 time.Sleep(time.Second)
}

func main() {

 //创建还有两个协程的pool
 p := work.New(2)

 //创建main协程的倒计时门闩
 var wg sync.WaitGroup
 wg.Add(100 * len(names))

 //多协程投递任务到pool
 for i := 0; i < 100; i++ {

  for _, name := range names {
   np := namePrinter{
    name: name,
   }
   go func() {
    p.Run(&np)
    wg.Done()
   }()

  }
 }

 //等待任务投递完成
 wg.Wait()

 fmt.Println("执行结束,关闭pool")
 p.ShutDown()

}

小结

自此,本文基于go语言的并发技术实现了一个简单的协程池,希望对你有所帮助。而go语言系列也到此告一段落。

我是 sharkchiliCSDN Java 领域博客专家开源项目---JavaGuide contributor ,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。 因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 "加群" 即可和笔者和笔者的朋友们进行深入交流。

本文使用 markdown.com.cn 排版

相关推荐
求知若饥5 分钟前
NestJS 项目实战-权限管理系统开发(六)
后端·node.js·nestjs
gb42152871 小时前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶1 小时前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
颜淡慕潇1 小时前
【K8S问题系列 |19 】如何解决 Pod 无法挂载 PVC问题
后端·云原生·容器·kubernetes
向前看-9 小时前
验证码机制
前端·后端
超爱吃士力架10 小时前
邀请逻辑
java·linux·后端
AskHarries12 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
isolusion13 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp14 小时前
Spring-AOP
java·后端·spring·spring-aop