前言:
先聊下线程池,线程池存在是为了规避频繁的线程创建和销毁的开销,那么协程池呢? 我们知道在go的gmp体系下,协程的创建开销是很低的。几年前社区里不少开源项目里,为了规避协程的创建销毁的开销, 重复morestack扩充栈空间问题来实现协程池。比如风靡一时的fasthttp框架和tidb分布式数据库也引用了协程池。
但去年我在pingcap的tidb里看到了猫哥提的pr, 里面详细对比了 加协程池和直接 go func(){}的性能对比,直接 go newproc1的性能要比加协程池还高一些。想想也能理解,协程池也是有channel的开销的,channel里不仅有锁消耗,而且还有因阻塞造成的调度消耗。
该文章后续仍在不断的更新修改中, 请移步到原文地址 http://xiaorui.cc/?p=5866
我们用golang协程池的场景?
回到我们的主题,我们的场景主要是并发控制! 对的,用协程池来控制下游的并发。比如下游的某个api同一时间最多可以抗住200个长轮询请求。你可以在协程池里控制最多200个goroutine来处理。
当然你也可以使用信号量来控制,没有选用信号量的原因主要是不能动态的控制worker,没有缓冲队列的设计。当然,这是个伪命题。
这里不能用令牌和漏桶来限制并发,因为你不确定请求啥时候执行完,如令牌桶不好控制好令牌的生成速度。
go协程池的实现:
我自己实现了一个go协程池, 代码扔到github里了,有兴趣的可以看看。https://github.com/rfyiamcool/gpool , 求点赞。
实现的思路很简单,就是需要一个channel做缓冲池,然后预先开启几个goroutine消费该channel。通过回调的方法来实现异步和阻塞方法。
// xiaorui.cc package main import ( "fmt" "sync" "time" "github.com/rfyiamcool/gpool" ) var ( wg = sync.WaitGroup{} ) func main() { gp, err := gpool.NewGPool(&gpool.Options{ MaxWorker: 5, // 最大的协程数 MinWorker: 2, // 最小的协程数 JobBuffer: 1, // 缓冲队列的大小 IdleTimeout: 120 * time.Second, // 协程的空闲超时退出时间 }) if err != nil { panic(err.Error()) } for index := 0; index < 1000; index++ { wg.Add(1) idx := index gp.ProcessAsync(func() { fmt.Println(idx, time.Now()) time.Sleep(1 * time.Second) wg.Done() }) } wg.Done() }
总结:
原本是打算写一个golang hystrix熔断器的,看过java hystrix熔断器的实现,第一个步骤就是构建线程池。但写着写着,懒了,就先协程池吧。