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

相关推荐
Minyy111 小时前
SpringBoot程序的创建以及特点,配置文件,LogBack记录日志,配置过滤器、拦截器、全局异常
xml·java·spring boot·后端·spring·mybatis·logback
画个大饼3 小时前
Go语言实战:快速搭建完整的用户认证系统
开发语言·后端·golang
iuyou️8 小时前
Spring Boot知识点详解
java·spring boot·后端
一弓虽9 小时前
SpringBoot 学习
java·spring boot·后端·学习
姑苏洛言9 小时前
扫码小程序实现仓库进销存管理中遇到的问题 setStorageSync 存储大小限制错误解决方案
前端·后端
光而不耀@lgy9 小时前
C++初登门槛
linux·开发语言·网络·c++·后端
方圆想当图灵9 小时前
由 Mybatis 源码畅谈软件设计(七):SQL “染色” 拦截器实战
后端·mybatis·代码规范
毅航10 小时前
MyBatis 事务管理:一文掌握Mybatis事务管理核心逻辑
java·后端·mybatis
我的golang之路果然有问题10 小时前
速成GO访问sql,个人笔记
经验分享·笔记·后端·sql·golang·go·database
柏油10 小时前
MySql InnoDB 事务实现之 undo log 日志
数据库·后端·mysql