写在文章开头
本篇文章算是对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
的大小,当协程都没有任务执行时,才会调用wg
的Done
方法,确保所有任务执行完成后,主协程才能退出:
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
}
关闭协程池
最后我们给出关于协程池关闭的实现,其逻辑比较简单:
- 关闭
channel
不再接受新任务。 - 调用
waitGroup
的Wait
方法等待所有协程执行完再返回。
scss
func (p *Pool) ShutDown() {
close(p.work)
p.wg.Wait()
}
测试代码
最后我们给出本文的测试代码,使用示例比较简单:
- 定义一个姓名切片,作为测试数据。
- 创建一个名为
namePrinter
的结构体,内部包含name
属性,该结构体会继承Worker
实现打印姓名的Task
方法。 - 创建一个
channel
和协程大小都为2的Pool
。 - 通过多协程循环遍历
name
切片并将其封装成namePrinter投递到chanel中。 - 协程池的协程消费这些打印姓名的任务。
- 调用
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语言系列也到此告一段落。
我是 sharkchili ,CSDN Java 领域博客专家 ,开源项目---JavaGuide contributor ,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。 因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 "加群" 即可和笔者和笔者的朋友们进行深入交流。
本文使用 markdown.com.cn 排版