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

Hi,大家好,我是明哥。

在自己学习 Golang 的这段时间里,我写了详细的学习笔记放在我的个人微信公众号 《Go编程时光》,对于 Go 语言,我也算是个初学者,因此写的东西应该会比较适合刚接触的同学,如果你也是刚学习 Go 语言,不防关注一下,一起学习,一起成长。

我的在线博客:http://golang.iswbm.com

我的 Github:github.com/iswbm/GolangCodingTime

在 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

case p.sem

go p.worker(task)

}

}

2、worker:用于执行任务

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

func (p *Pool) worker(task func()) {

defer func() {

for {

task()

task =

}

}

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于Python协程,我来简单介绍一下它的概念和使用方法。 Python协程指的是使用生成器(generator)实现协程,通过yield关键字实现协程的暂停和恢复。协程的使用可以有效地提升Python程序的并发性能,特别是在IO密集型任务中,协程可以大大减少线程切换的开销。 下面是一个简单的Python协程示例: ```python def coroutine(): while True: value = yield print('Received value:', value) c = coroutine() next(c) c.send(10) c.send('Hello') ``` 在这个示例中,我们定义了一个名为coroutine的生成器函数,其中使用了yield关键字来暂停和恢复协程的执行。在主程序中,我们首先通过next(c)来启动协程的执行,然后使用c.send(value)来向协程发送数据,协程会在yield处暂停执行,并打印接收到的数据。 除了使用yield关键字来实现协程Python还提供了async/await关键字来更方便地编写协程。使用async/await可以将协程的代码看作是顺序执行的,而不需要手动调用生成器的方法。 下面是一个使用async/await关键字的协程示例: ```python async def coroutine(): while True: value = await asyncio.sleep(1) print('Received value:', value) asyncio.run(coroutine()) ``` 在这个示例中,我们使用async/await关键字定义了一个名为coroutine的协程函数,并在其中使用await asyncio.sleep(1)来模拟协程的IO操作,每秒钟打印一次接收到的数据。然后在主程序中使用asyncio.run()方法来启动协程的执行。 需要注意的是,Python协程的使用需要结合asyncio库来进行,asyncio提供了事件循环(event loop)、协程调度器、异步IO等功能,可以方便地实现协程的并发调度和异步IO操作。 希望这些信息对您有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值