显然不需要,goroutine的初衷就是轻量级的线程,为的就是让你随用随起,结果你又搞个池子来,这不是脱裤子放屁么?你需要的是限制并发,而协程池是一种违背了初衷的方法。

池化要解决的问题一个是频繁创建的开销,另一个是在等待时占用的资源。goroutine 和普通线程相比,创建和调度都不需要进入内核,也就是创建的开销已经解决了。同时相比系统线程,内存占用也是轻量的。所以池化技术要解决的问题goroutine 都不存在,为什么要创建 goroutine pool 呢?

如果因为 goroutine 持有资源而要去创建goroutine pool,那只能说明代码的耦合度较高,应该为这类资源创建一个goroutine-safe的对象池,而不是把goroutine本身池化。


额,我感觉我应该能够说一说这个问题。我以前也觉得Go是不需要复用goroutine,毕竟据说「非常轻量级」……但是,其实很多时候话不能说死了。应该是98.72531%的场景下,你不需要goroutine池,剩下不到2%的case,大多数人应该也遇不到,所以说「不需要协程池」,几乎是正确的。

那剩下不到2%的case是啥呢?——超高并发低延时的case,比如做网关。我们的网关目前单机2W+的QPS,是非常极端的场景了。有人可能网上blog看多了,觉得2W好意思拿出来说,别人都是百万级,C100K。额,具体区别就不多说了,如果认识人,可以问问微博feed流,百度大搜等等高峰期单机有多大的QPS…

说这个的意思就是,我用Go的生产环境中应对过大流量,所以知道Go的Scheduler还是不够好,Go的协程调度还是挺坑的,感兴趣可以搜搜TiDB的吐槽,他们也是重度用户,极端压力下的goroutine调度,真心让人抓狂。

所以,这种case下,goroutine要省著用,不然你pprof里大部分时间就花在runtime.findRunableG上了。最简单的例子,假设你每秒new 5W个goroutine,但是每个goroutine至少运行2秒,你系统内的g就会堆积,越来越多,然后scheduler负担越来越重,导致每个goroutine耗时比2秒更长,导致堆积更加严重,然后你的系统就崩溃了。所以,这种时候goroutine一定要省著用。当然你说你可以限流,但是那也得保证goroutine回收的速率不高于生产的速率。

不过以上case都是极端场景下才会遇到,对于98%几十几百QPS的服务,那肯定就不需要了


Go自从出生就身带「高并发」的标签,其并发编程就是由groutine实现的,因其消耗资源低,性能高效,开发成本低的特性而被广泛应用到各种场景,例如服务端开发中使用的HTTP服务,在golang net/http包中,每一个被监听到的tcp链接都是由一个groutine去完成处理其上下文的,由此使得其拥有极其优秀的并发量吞吐量。

一般情况下,goroutine在操作系统上只要你的硬体资源够它是可以无限启动的。但是如果出现大规模的启动goroutine的情况会造成大量占用系统资源,我们知道普通的部署一个golang应用的时候操作系统不仅仅会运行golang程序还有其他辅助的程序运行,所以理论上讲工作池的目的就是为了限制golang的启动数量,保证不会出现硬体计算资源溢出的情况。

也就是说,虽然go语言在调度Goroutine已经优化的非常完成,并且Goroutine作为轻量级执行流程,也不需要CPU调度器的切换,我们一般在使用的时候,如果想处理一个分支流程,直接go一下即可。但是,如果无休止的开辟Goroutine依然会出现高频率的调度Groutine,那么依然会浪费很多上下文切换的资源,导致做无用功。所以设计一个Goroutine池限制Goroutine的开辟个数在大型并发场景还是必要的。

所以个人观点:有些场合还是可以使用协程池的。


深夜失眠修改下答案,想说说题主说的只要控制gorountine数量的问题,我觉得就像sync.Pool一样,Pool中的数据在每次GC的时候都会清掉,所以不能用在一些需要保持连接的场景下,但是存在即合理,sync.Pool就是个临时对象池,可以减轻程序频繁创建对象的消耗,以减轻垃圾回收的压力,gorountine是不是也是一样呢,虽然起一个协程的开销很小,但是在极限情况下,合理复用总是没错的,这事儿还是得结合具体场景来选择,简单限制数量,开发起来快速,彻底池化,可以压榨极限性能。

可能有考虑不周的地方,只是晚上睡不著的一点思考

==========================

原回答:

先明确一些概念:

goroutine是如何调度的,这个需要去了解一下协程的 G-P-M 调度模型,网上已经有太多了,我就不多说了,官方给出的链接:

https://docs.google.com/document/d/1ETuA2IOmnaQ4j81AtTGT40Y4_Jr6_IDASEKg0t0dBR8/edit#?

docs.google.com

贴一个中文的比较易懂的链接吧

Go 系列文章4 : 调度器?

xargin.com

我们可以把 goroutine看成是一种协程,但是goroutine在底层实现上跟传统意义上的协程还是有区别的(传统协程是用户级线程模型,而goroutine因为有了 Go Scheduler,在底层上应该属于两级线程模型,不在这个问题的讨论范围内,这里不展开说了,只要记得gorountine 不完全等于协程就好了)

下面来说说这个问题, Golang开发需要协程池么?

应该结合具体场景来讨论这个问题,如果仅仅是一个小型项目,你的机器配置足够支持大量goroutine,协程池就显得没什么必要了,那这个回答也不用写下去了,如果是一个有外部干扰的项目呢?比如

net/http包中的Server对每一个请求都会起一个goroutine ,而goroutine是需要内存的,假如我们的项目需要压榨伺服器的极限还尽可能的保持可用,最先想到的,就需要针对内存使用做一些限制。

考虑下面一个场景: 每一个请求都是一个gorountine,某黑客对你的机器DDOS,发起了上百万甚至千万请求(只是描述一种极限情况,打个比方...),那么伺服器就要起上千万的goroutine(在业务逻辑中,经常会启动多个goroutine来实现需求),这时对内存的消耗是极大的,协程越多,GC的负担也越大的,一旦内存耗尽,服务也就基本崩溃了...

(歪一下,GC这事儿,可以看看这个)

Go 的垃圾回收机制在实践中有哪些需要注意的地方??

www.zhihu.com图标

这个时候!首先要做一些简单的,比如漏斗限流,限制客户端对某一行为的频率等等...

如果加上协程池会不会更好呢?

上面的主要问题实际上就是goroutine的数量过多导致资源侵占,那要解决这个问题就要限制运行的goroutine数量,合理复用,节省资源,具体就是——goroutine池

使用协程池将让多余的请求进入排队状态,等待池中有空闲协程的时候来处理,这是模拟的I/O多路复用机制,贴一个原理:

通俗讲解 非同步,非阻塞和 IO 复用?

www.zybuluo.com

这样就可以通过控制协程池的大小,来控制内存的消耗,让其在极端状态下,也尽可能的保证服务的可用性。

代码的话其他答案里写了有开源的,我就不写啦~

点个赞再走呗~


在系统开发过程中,我们经常会用到池化技术来减少系统消耗,提升系统性能。对象池通过复用对象来减少创建对象、垃圾回收的开销。连接池(资料库连接池、Redis连接池、HTTP连接池)通过复用TCP连接来减少创建和释放连接的时间。线程池通过复用线程提升性能。简单来说,池化技术就是通过复用来提升性能。

池化技术本来是用来复用提升性能的。

goroutine的开销极小,不需要通过压榨goroutine的复用来进行提升性能。

但是根据场景的不同,有些人会把池子拿来做限流、限制并发等操作,这个时候是需要的。

我目前在用这个库,通过限制waitgroup的大小来限制并发数。

remeh/sizedwaitgroup


1.的确可减少获取协程的成本。

2.限制并发数(当然可以通过一个简单的计数搞定)。

3.更大的作用在有大量长连接时,把逻辑处理丢给协程池,减少栈扩容,把栈扩容限定在协程池的协程中,减少GC压力。

需要不需要,看场景。


在大多数场景中不需要使用协程池,原因如下:

- `goroutine`非常轻量,默认2k,使用`go func()`很难成为性能瓶颈

- 现代架构一般都使用分散式架构解决高并发问题,单机性能再高也很容易达到上限

- 一般上游会限流、或设置最大协程数,限制并发数,这时考虑性能没有意义

- 引入协程池,会带来额外的复杂性,违背了初衷

如果在某些场景需要追求性能,那么可以使用协程池实现资源的复用,提高性能如:fasthttp使用协程池之后性能大大提高。


github有现成的,好像也是国人开发的。

https://github.com/panjf2000/ants


看你做什么,一般的写写webapi还是不用协程池的


99%的场景都不需要,goroutine is cheap

在性能非常严苛的场景下,为了避免过多的调度开销,可以考虑池化


还是有不少场景需要的,比如需要指定并发数,sql.SetMaxConn


分应用场合:

1:对内的应用,不需要,尽情开吧,反正不会考虑外部各种问题,都是特定的需求

2:对外的应用:要不从源头限制协程数量,超过一定大小不处理直接PASS,例如TCP监听;要么需要一个协程池,任务分发,不过go的chan太恶心,是阻塞式的,容易全部堵死


极端情况下需要,每个goroutine分配2KB的内存,线程太多,例如10,100w,内存告急,就会对GC造成极大的负担,太过于频繁地进行GC,性能瓶颈在所难免(并行GC 太频繁调用也难免),再说了,GC夜是goroutine,内存不足,Go调度器就会阻塞goroutine,GPM模型的P的Local队列积压,又导致内存溢出,这是个是个死循环,最坏可能会导致程序挂掉。

解决办法复用goroutine,池化(IO多路复用),减少线程数量,减轻线程调度压力,节省内存。


golang哪有协程?

golang只有纤程,goroutine是纤程,你只能做类似线程池一样的纤程池

做不到await那种协程的语法糖

夜雀:你们这是在玩文字游戏


计算密集型时某些模式需要,也就是说你需要处理的数据,从单个来看数据处理少,从整体来看却是大量的。

一种方式是数据生产方来选择处理协程序,也就是每个协程都有自己chan,选择协程就是选择chan,所谓协程池就是chan池

另外一种方式把选择协程交给语言,即多协程竞争单chan,发送者只需要将数据发送到

这个chan上

其实普通使用用后者就可以了,简单。

不过真要分析,还是可以说说的

因为chan本质还是一个队列加一把锁,不管机制优化得有多好,锁就是锁,锁就意味著串列化。不仅锁住了其他发送者,也锁住了其他的接收者,所以同步代价还是有的。

我们知道P和M基本是固定的,正在执行的G也是固定的,而等待执行的G(sudoG)却是不固定的

要起床上班了,后续补充

如果有谬误之处,请提点指出,谢谢


基本不需要吧。Golang中协程的诞生就是为了消灭线程的调度管理造成的性能损失,办法就是线程池。

Golang中的协程已经变得很轻量了,如果再去弄个协程池,那么同样协程池中也有任务(加入称为协协程吧)需要被调度,到时候你是不是又要弄个`协协程池`呢,你弄的协协程的调度性能可能还不如协程的调度。。。

所以我认为弄个协程池的得不偿失的,耗费人力结果可能性能更差


有future抽象,用future。


推荐阅读:
相关文章