Go 常见并发模式实现(三):通过无缓冲通道创建协程池

Wesley13
• 阅读 434

Go 常见并发模式实现(三):通过无缓冲通道创建协程池

上篇教程学院君给大家演示了如何通过缓冲通道实现共享资源池,今天,我们来看另一个并发模式的 Go 语言实现 —— 通过无缓冲通道实现协程(goroutine)池。

这些协程池通常用于并发执行一组任务,最终组合起来完成某个功能。在这种情况下,使用无缓冲通道要比使用缓冲通道好,因为既不需要任务队列,也不需要一组协程配合执行,并且方便知道什么时候协程池正在执行任务,如果协程池中的所有协程都在忙,无法处理新的任务,也能及时通过通道通知调用者(分配给无缓冲通道的任务未处理会阻塞后续分配)。另外,使用无缓冲通道不会有任务在队列中丢失或卡住,所有任务都会被处理。

创建一个 worker 目录,并在其中新建一个 worker.go 文件,根据上述思路,编写一段无缓冲通道创建协程池的示例代码如下:

package workerimport "sync"type Worker interface {    Task()}type Pool struct {    work chan Worker    wg sync.WaitGroup}func New(maxGoroutines int) *Pool {    p := Pool{        work: make(chan Worker),    }    p.wg.Add(maxGoroutines)    for i := 0; i < maxGoroutines; i++ {        go func() {            for w := range p.work {                w.Task()            }            p.wg.Done()        }()    }    return &p}func (p *Pool) Run(w Worker)  {    p.work <- w}func (p *Pool) Shutdown() {    close(p.work)    p.wg.Wait()}

在上述代码中,我们定义了一个表示协程池的 Pool 结构体,其中 work 是一个无缓冲通道类型(该类型需要实现 Woker 接口),用于表示需要协程池执行的任务,wg 是一个 sync.WaitGroup 类型,用于控制协程池所有协程的执行和退出。

Pool 结构体定义了三个方法,分别是初始化协程池的 New 方法,分配任务给协程池的 Run 方法,以及关闭协程池的 Shutdown 方法,我们重点关注 New 方法。该方法接收一个 maxGoroutines 参数表示协程池中协程的最大数量,在初始化 Poolwork 属性时,没有指定缓冲值,表明其无缓冲通道类型:

p := Pool{    work: make(chan Worker),}

我们会让每个协程执行对应的任务方法(如果协程池有分配任务的话,否则会阻塞):

p.wg.Add(maxGoroutines)for i := 0; i < maxGoroutines; i++ {    go func() {        for w := range p.work {            w.Task()        }        p.wg.Done()    }()}

注:我们可以通过协程池的 Run 方法将任务分配给协程池。

上述代码会异步执行,所以不影响 New 方法返回实例化后的 Pool 对象。

接下来,我们编写一段调用该协程池执行任务的入口程序 work.go

package mainimport (    "log"    "sync"    "test/worker"    "time")var langs = []string{    "Golang",    "PHP",    "JavaScript",    "Python",    "Java",}type langPrinter struct {    lang string}func (m *langPrinter) Task() {    log.Println(m.lang)    time.Sleep(time.Second)}func main() {    p := worker.New(2)    var wg sync.WaitGroup    wg.Add(5 * len(langs))    for i := 0; i < 5; i++ {        for _, lang := range langs {            lp := langPrinter{lang}            go func() {                p.Run(&lp)                wg.Done()            }()        }    }    wg.Wait()    p.Shutdown()}

在这段代码中,我们定义了一个实现 Worker 接口的 langPrinter 类(实现 Task 方法),用于执行打印编程语言的任务(耗时1s)。然后我们在入口函数中,初始化协程池,指定其大小为 2,然后遍历 langs 切片,依次将基于 lang 值初始化的 langPrinter 对象作为任务实例分配给协程池去执行(从初始化时堵塞的地方开始执行),当然这个分配工作也是通过协程异步执行的,尽管每组可以分配 5 个任务,但是由于协程池中定义的是无缓冲通道,并且协程池的大小是 2,所以一次只能并发执行两个任务。所有分配给协程池的任务执行完成后,会关闭协程池,释放资源。

调用上述入口程序 work.go,输出结果如下:

Go 常见并发模式实现(三):通过无缓冲通道创建协程池

通过上述输出,我们也可以验证每次协程池只能并发执行两个任务(每秒钟打印两个结果)。和缓冲通道调整缓冲值来调节并发能力不同,这里只能通过调整协程池大小来调节程序并发能力。

附:本篇教程示例代码目录结构如下:

--go (项目根目录 ~/Development/go)  |--src      |--test          |--worker              |--worker.go          |--work.go          |--go.mod

(全文完)

长按下面的二维码,即可订阅学院君最新发布的 Go Web 编程系列教程:

Go 常见并发模式实现(三):通过无缓冲通道创建协程池

关于本系列教程的更多动态,请点击页面左下角的「阅读原文」链接查看。

本文分享自微信公众号 - xueyuanjun(geekacademy)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这