用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 排版

相关推荐
【D'accumulation】2 分钟前
令牌主动失效机制范例(利用redis)注释分析
java·spring boot·redis·后端
2401_8543910811 分钟前
高效开发:SpringBoot网上租赁系统实现细节
java·spring boot·后端
Cikiss20 分钟前
微服务实战——SpringCache 整合 Redis
java·redis·后端·微服务
Cikiss21 分钟前
微服务实战——平台属性
java·数据库·后端·微服务
OEC小胖胖35 分钟前
Spring Boot + MyBatis 项目中常用注解详解(万字长篇解读)
java·spring boot·后端·spring·mybatis·web
2401_857617621 小时前
SpringBoot校园资料平台:开发与部署指南
java·spring boot·后端
计算机学姐1 小时前
基于SpringBoot+Vue的在线投票系统
java·vue.js·spring boot·后端·学习·intellij-idea·mybatis
Yvemil72 小时前
MQ 架构设计原理与消息中间件详解(二)
开发语言·后端·ruby
2401_854391082 小时前
Spring Boot大学生就业招聘系统的开发与部署
java·spring boot·后端
虽千万人 吾往矣3 小时前
golang gorm
开发语言·数据库·后端·tcp/ip·golang