顯然不需要,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。


推薦閱讀:
相關文章