如何手动实现一个协程池?

简介:

云栖号资讯:【点击查看更多行业资讯
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!


在 Golang 中要创建一个协程是一件无比简单的事情,你只要定义一个函数,并使用 go 关键字去执行它就行了。

如果你接触过其他语言,会发现你在使用使用线程时,为了减少线程频繁创建销毁还来的开销,通常我们会使用线程池来复用线程。

池化技术就是利用复用来提升性能的,那在 Golang 中需要协程池吗?

在 Golang 中,goroutine 是一个轻量级的线程,他的创建、调度都是在用户态进行,并不需要进入内核,这意味着创建销毁协程带来的开销是非常小的。

因此,我认为大多数情况下,开发人员是不太需要使用协程池的。

但也不排除有某些场景下是需要这样做,因为我还没有遇到就不说了。

抛开是否必要这个问题,单纯从技术的角度来看,我们可以怎样实现一个通用的协程池呢?

下面就来一起学习一下我的写法

首先定义一个协程池(Pool)结构体,包含两个属性,都是 chan 类型的。

一个是 work,用于接收 task 任务

一个是 sem,用于设置协程池大小,即可同时执行的协程数量

type Pool struct {
    work chan func()   // 任务
    sem  chan struct{} // 数量
}

然后定义一个 New 函数,用于创建一个协程池对象,有一个细节需要注意

work 是一个无缓冲通道

而 sem 是一个缓冲通道,size 大小即为协程池大小

func New(size int) *Pool {
    return &Pool{
        work: make(chan func()),
        sem:  make(chan struct{}, size),
    }
}

最后给协程池对象绑定两个函数

1、NewTask:往协程池中添加任务

当第一次调用 NewTask 添加任务的时候,由于 work 是无缓冲通道,所以会一定会走第二个 case 的分支:使用 go worker 开启一个协程。

func (p *Pool) NewTask(task func()) { 
    select {
        case p.work <- task:
        case p.sem <- struct{}{}:
            go p.worker(task)
    }
}

2、worker:用于执行任务

为了能够实现协程的复用,这个使用了 for 无限循环,使这个协程在执行完任务后,也不退出,而是一直在接收新的任务。

func (p *Pool) worker(task func()) { 
    defer func() { <-p.sem }()
    for {
        task()
        task = <-p.work
    }
}

这两个函数是协程池实现的关键函数,里面的逻辑很值得推敲:

1、如果设定的协程池数大于 2,此时第二次传入往 NewTask 传入task,select case 的时候,如果第一个协程还在运行中,就一定会走第二个case,重新创建一个协程执行task

2、如果传入的任务数大于设定的协程池数,并且此时所有的任务都还在运行中,那此时再调用 NewTask 传入 task ,这两个 case 都不会命中,会一直阻塞直到有任务执行完成,worker 函数里的 work 通道才能接收到新的任务,继续执行。

以上便是协程池的实现过程。

使用它也很简单,看下面的代码你就明白了

func main()  {
    pool := New(128)
    pool.NewTask(func(){
        fmt.Println("run task")
    })
}

为了让你看到效果,我设置协程池数为 2,开启四个任务,都是 sleep 2 秒后,打印当前时间。

func main()  {
    pool := New(2)

    for i := 1; i <5; i++{
        pool.NewTask(func(){
            time.Sleep(2 * time.Second)
            fmt.Println(time.Now())
        })
    }

    // 保证所有的协程都执行完毕
    time.Sleep(5 * time.Second)
}

执行结果如下,可以看到总共 4 个任务,由于协程池大小为 2,所以 4 个任务分两批执行(从打印的时间可以看出)

2020-05-24 23:18:02.014487 +0800 CST m=+2.005207182
2020-05-24 23:18:02.014524 +0800 CST m=+2.005243650
2020-05-24 23:18:04.019755 +0800 CST m=+4.010435443
2020-05-24 23:18:04.019819 +0800 CST m=+4.010499440

【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqh.aliyun.com/live

立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK

原文发布时间:2020-06-11
本文作者:王一白
本文来自:“掘金”,了解相关信息可以关注“掘金”

相关文章
|
Go 调度 机器学习/深度学习
|
5月前
|
监控 负载均衡 算法
Golang深入浅出之-Go语言中的协程池设计与实现
【5月更文挑战第3天】本文探讨了Go语言中的协程池设计,用于管理goroutine并优化并发性能。协程池通过限制同时运行的goroutine数量防止资源耗尽,包括任务队列和工作协程两部分。基本实现思路涉及使用channel作为任务队列,固定数量的工作协程处理任务。文章还列举了一个简单的协程池实现示例,并讨论了常见问题如任务队列溢出、协程泄露和任务调度不均,提出了解决方案。通过合理设置缓冲区大小、确保资源释放、优化任务调度以及监控与调试,可以避免这些问题,提升系统性能和稳定性。
148 6
|
5月前
|
存储 Go 调度
听说90%的人都没搞定手撕协程池这道面试题!
听说90%的人都没搞定手撕协程池这道面试题!
|
Java 程序员 Go
grpool goroutine池详解 | 协程管理
goroutine协程非常轻量级,这也是为什么go支持高并发,但是goroutine频繁创建销毁对GC的压力比较大。
177 0
grpool goroutine池详解 | 协程管理
|
Go 调度 监控
golang 裸写一个pool池控制协程的大小
这几天深入的研究了一下golang 的协程,读了一个好文 http://mp.weixin.qq.com/s?__biz=MjM5OTcxMzE0MQ==&mid=2653369770&idx=1&sn=044be64c577a11a9a13447b373e80082&chksm=bce4d5b0...
1485 0
|
4月前
|
Go Python
使用python实现一个用户态协程
【6月更文挑战第28天】本文探讨了如何在Python中实现类似Golang中协程(goroutines)和通道(channels)的概念。文章最后提到了`wait_for`函数在处理超时和取消操作中的作
41 1
使用python实现一个用户态协程
|
12天前
|
调度 Python
python3 协程实战(python3经典编程案例)
该文章通过多个实战案例介绍了如何在Python3中使用协程来提高I/O密集型应用的性能,利用asyncio库以及async/await语法来编写高效的异步代码。
11 0
|
3月前
|
数据库 开发者 Python
实战指南:用Python协程与异步函数优化高性能Web应用
【7月更文挑战第15天】Python的协程与异步函数优化Web性能,通过非阻塞I/O提升并发处理能力。使用aiohttp库构建异步服务器,示例代码展示如何处理GET请求。异步处理减少资源消耗,提高响应速度和吞吐量,适用于高并发场景。掌握这项技术对提升Web应用性能至关重要。
76 10
|
3月前
|
数据处理 Python
深入探索:Python中的并发编程新纪元——协程与异步函数解析
【7月更文挑战第15天】Python 3.5+引入的协程和异步函数革新了并发编程。协程,轻量级线程,由程序控制切换,降低开销。异步函数是协程的高级形式,允许等待异步操作。通过`asyncio`库,如示例所示,能并发执行任务,提高I/O密集型任务效率,实现并发而非并行,优化CPU利用率。理解和掌握这些工具对于构建高效网络应用至关重要。
43 6
|
3月前
|
大数据 数据处理 API
性能飞跃:Python协程与异步函数在数据处理中的高效应用
【7月更文挑战第15天】在大数据时代,Python的协程和异步函数解决了同步编程的性能瓶颈问题。同步编程在处理I/O密集型任务时效率低下,而Python的`asyncio`库支持的异步编程利用协程实现并发,通过`async def`和`await`避免了不必要的等待,提升了CPU利用率。例如,从多个API获取数据,异步方式使用`aiohttp`并发请求,显著提高了效率。掌握异步编程对于高效处理大规模数据至关重要。
47 4