你知道 GO 中的 協(xié)程可以無(wú)止境的開(kāi)嗎?
GO語(yǔ)言天生高并發(fā)的語(yǔ)言,那么是不是使用 go 開(kāi)辟協(xié)程越多越好的,那么在 go 里面,協(xié)程是不是可以開(kāi)無(wú)限多個(gè)呢?
那么我們就一起來(lái)看看嘗試寫(xiě)寫(xiě) demo 吧
嘗試開(kāi)辟盡可能多的 協(xié)程
寫(xiě)一個(gè) demo ,循環(huán)開(kāi) 1 << 31 - 1
個(gè)協(xié)程看看會(huì)是什么效果
func?main()?{
?goroutineNum?:=?math.MaxInt32
?for?i?:=?0;?i?<?goroutineNum;?i++?{
??go?func(i?int)?{
???fmt.Println("?i?==?",?i,?"func?==?",?runtime.NumGoroutine())
??}(i)
?}
}
執(zhí)行后,我人都傻了,直接是沒(méi)有輸出,2 核 1 G 的服務(wù)器直接卡死 , 感興趣的 xdm 可以嘗試一波
這里說(shuō)一下,出現(xiàn)上述現(xiàn)象的原因是:
我們迅速的瘋狂開(kāi)辟協(xié)程,又不控制并發(fā)數(shù)量,那么在那段很短的時(shí)間里面,go 程序會(huì)盡可能多的占用操作系統(tǒng)資源,直到被操作系統(tǒng)主動(dòng)殺掉
一旦主協(xié)程被殺掉,那么其他的協(xié)程也全部 game over , 因?yàn)樗麄冋加玫馁Y源是用戶態(tài)的共享資源,一個(gè)協(xié)程掛掉,是會(huì)影響到其他協(xié)程的
嘗試控制協(xié)程數(shù)量
咱們實(shí)現(xiàn)的方法是,使用 channel ,設(shè)置 channel 的緩沖個(gè)數(shù)來(lái)控制實(shí)際并發(fā)的協(xié)程個(gè)數(shù),一起來(lái)看看是否有效果
func?processGo(i?int,?ch?chan?struct{})?{
?fmt.Println("?i?==?",?i,?"func?==?",?runtime.NumGoroutine())
?<-ch
}
func?main()?{
?goroutineNum?:=?math.MaxInt32
?ch?:=?make(chan?struct{},?5)
?for?i?:=?0;?i?<?goroutineNum;?i++?{
??ch?<-?struct{}{}
??go?processGo(i,?ch)
?}
}
效果見(jiàn)如下截圖,由于數(shù)據(jù)打印太長(zhǎng),如下為部分?jǐn)?shù)據(jù)

這里我們可以看到,加入并發(fā)控制后,效果還是很明顯的,至少我的服務(wù)器不會(huì)被卡死了
通過(guò)打印我們可以看出來(lái),總共 6 個(gè)協(xié)程,其中有 5 個(gè)是子協(xié)程,1 個(gè)是主協(xié)程
我們這里,使用 channel 的方式來(lái)控制并發(fā),go 協(xié)程的創(chuàng)建速度 依賴于 for 循環(huán)的速度,而 for 循環(huán)的速度是被 channel 控制住了 ,channel 的速度實(shí)際上又被實(shí)際處理事情的協(xié)程的處理速度控制著,因此,我們可以保證在同一個(gè)時(shí)間內(nèi),并發(fā)運(yùn)行的協(xié)程總共是 6 個(gè)
但是這就夠了么, nonono , 我們可以再來(lái)看一個(gè)例子
我們?cè)O(shè)置在循環(huán)的個(gè)數(shù)為 10 ,比剛才的值小了很多,代碼邏輯保持一致
func?main()?{
?goroutineNum?:=?10
?ch?:=?make(chan?struct{},?5)
?for?i?:=?0;?i?<?goroutineNum;?i++?{
??ch?<-?struct{}{}
??go?processGo(i,?ch)
?}
}
執(zhí)行程序看效果
?go?run?main.go
?i?==??4?func?==??6
?i?==??5?func?==??6
?i?==??6?func?==??6
?i?==??7?func?==??6
?i?==??8?func?==??6
我們發(fā)現(xiàn)輸出并不是我們想要的 , 出現(xiàn)這個(gè)的原因是主協(xié)程 循環(huán) 10 次完畢之后,就會(huì)馬上退出程序,進(jìn)而子協(xié)程也隨之退出,這個(gè)問(wèn)題需要解決
嘗試加入 sync 同步機(jī)制,讓主協(xié)程等一下子協(xié)程
之前我們有分享到 go 中的一個(gè)知識(shí)點(diǎn),可以使用 sync 來(lái)一起控制同步 , 就是使用 sync.WaitGroup
,不知道 xdm 是否還記得,不記得沒(méi)關(guān)系,咱們今天再使用一遍,看看效果
加入 sync 機(jī)制,循環(huán)的時(shí)候,需要開(kāi)辟協(xié)程時(shí),則 sync.Add
協(xié)程結(jié)束的時(shí)候,sync.Done
主協(xié)程循環(huán)完畢之后,等待子協(xié)程完成自己的事情,使用 sync.Wait
func?processGo(i?int,?ch?chan?struct{})?{
?fmt.Println("?i?==?",?i,?"func?==?",?runtime.NumGoroutine())
?<-ch
?wg.Done()
}
var?wg?=?sync.WaitGroup{}
func?main()?{
?goroutineNum?:=?10
?ch?:=?make(chan?struct{},?5)
?for?i?:=?0;?i?<?goroutineNum;?i++?{
??ch?<-?struct{}{}
??wg.Add(1)
??go?processGo(i,?ch)
?}
?wg.Wait()
}
上述代碼中,我們可以簡(jiǎn)單理解 sync 的使用, sync.Add 就是添加需要等待多少個(gè)子協(xié)程結(jié)束, sync.Done 就是當(dāng)前的子協(xié)程結(jié)束了,減去 1 個(gè)協(xié)程, sync.Wait 就是等待 子協(xié)程的個(gè)數(shù)最終變成 0 ,則認(rèn)為子協(xié)程全部關(guān)閉
運(yùn)行程序來(lái)查看效果
?go?run?main.go
?i?==??4?func?==??6
?i?==??5?func?==??6
?i?==??6?func?==??6
?i?==??7?func?==??6
?i?==??8?func?==??6
?i?==??9?func?==??6
?i?==??0?func?==??5
?i?==??1?func?==??4
?i?==??2?func?==??3
?i?==??3?func?==??2
嘗試做的更加可控一些更加優(yōu)秀一些
我們可以思考一下,上面的邏輯是不停的有協(xié)程在創(chuàng)建,也不停的有協(xié)程在被銷毀,這樣還是很耗資源的,我們是否可以固定設(shè)置具體的協(xié)程在做事情,并且將發(fā)送數(shù)據(jù)和處理數(shù)據(jù)進(jìn)行一個(gè)分離呢?
就類似于生產(chǎn)者和消費(fèi)者一樣
咱們來(lái)嘗試寫(xiě)一個(gè) demo
專門(mén)寫(xiě)一個(gè)函數(shù)用于分發(fā)任務(wù)
分發(fā)任務(wù)之前先開(kāi)辟好對(duì)應(yīng)的協(xié)程,等待任務(wù)進(jìn)來(lái)
func?processGo(i?int,?ch?chan?struct{})?{
?for?data?:=?range?ch?{
??fmt.Println("?i?==?",?data,?"func?==?",?runtime.NumGoroutine())
??wg.Done()
?}
}
func?distributeTask(ch?chan?struct{})?{
?wg.Add(1)
?ch?<-?struct{}{}
}
var?wg?=?sync.WaitGroup{}
func?main()?{
?goroutineNum?:=?2
?taskNum?:=?math.MaxInt32
?ch?:=?make(chan?struct{})
?//?先開(kāi)辟好協(xié)程?等待處理數(shù)據(jù)
?for?i?:=?0;?i?<?goroutineNum;?i++?{
??go?processGo(i,?ch)
?}
?//?分發(fā)事項(xiàng)
?for?i?:=?0;?i?<?taskNum;?i++?{
??distributeTask(ch)
?}
?wg.Wait()
}
此處使用 sync 控制的同步,可以說(shuō)是 對(duì)應(yīng)的是任務(wù)數(shù)量, 主協(xié)程是等待所有分發(fā)的任務(wù)數(shù)都被完成了,主協(xié)程才關(guān)閉程序
執(zhí)行程序查看效果
?go?run?main.go

程序正常運(yùn)行沒(méi)有毛病,這樣做的話,我們可以將分發(fā)任務(wù)和處理任務(wù)進(jìn)行分離,還大大減少了不必要的協(xié)程切換
對(duì)于如上案例做一個(gè)比喻
channel + sync 的案例 :
最上面的第一種案例,就是相當(dāng)于動(dòng)態(tài)雇傭 5 個(gè)工人,有任務(wù)的時(shí)候,工人就上去做,做完了自己下崗就得了,反正我這里只容納 5 個(gè)工人,且每個(gè)工人做完 1 個(gè)任務(wù)就得走
分發(fā)任務(wù)和處理數(shù)據(jù)的任務(wù)分離案例 :
最后的這個(gè)案例,就是固定的雇傭 2 個(gè)工人干活,項(xiàng)目經(jīng)理就不停的扔任務(wù)進(jìn)行來(lái),這倆人就瘋狂的干
xdm ,go 里面不能濫用協(xié)程,需要控制好 go 協(xié)程的數(shù)量
歡迎點(diǎn)贊,關(guān)注,收藏
朋友們,你的支持和鼓勵(lì),是我堅(jiān)持分享,提高質(zhì)量的動(dòng)力

好了,本次就到這里
技術(shù)是開(kāi)放的,我們的心態(tài),更應(yīng)是開(kāi)放的。擁抱變化,向陽(yáng)而生,努力向前行。
我是阿兵云原生,歡迎點(diǎn)贊關(guān)注收藏,下次見(jiàn)~