淺析Golang開(kāi)發(fā)中g(shù)oroutine的正確使用姿勢(shì)
很多初級(jí)的Gopher在學(xué)習(xí)了goroutine之后,在項(xiàng)目中其實(shí)使用率不高,尤其一些跨語(yǔ)言過(guò)來(lái)的人,對(duì)并發(fā)編程理解不深入,可能很多人只知道go func(),或者掌控不夠,謹(jǐn)慎一些,盡量少使用或者不使用,用的話就是go func(),主要列一下我們這邊的主要使用方法。
goroutine在項(xiàng)目中的使用方法
看一下樣例代碼,實(shí)際上,我們生產(chǎn)環(huán)境中就是這么使用的。
package logic import ( "context" "fmt" "sync" "time" ) type UserData struct { Age int Name string Postion string } type ServerLogic struct { ctx context.Context cancel func() waiter sync.WaitGroup ch chan UserData } func NewServerLogic(logCtx *context.Context, worker int, queue int) *ServerLogic { logic := &ServerLogic{} logic.InitWorker(worker, queue) return logic } func (this *ServerLogic) InitWorker(workers int, queue int) { this.ch = make(chan UserData, queue) this.ctx, this.cancel = context.WithCancel(context.Background()) this.waiter.Add(workers) for i := 0; i < workers; i++ { go this.Proc() } } func (this *ServerLogic) Proc() { defer this.waiter.Done() for { select { case t := <-this.ch: this.Dothing(t) case <-this.ctx.Done(): return } } } func (this *ServerLogic) Dothing(data UserData) error { //do code time.Sleep(time.Second*30) return nil } func (this *ServerLogic) Close() { this.cancel() this.waiter.Wait() } func (this *ServerLogic) PutData(user UserData) error { select { case this.ch<-user: return nil default: return fmt.Errorf("queue overflow") } }
如果有人想直接使用的話,只需要把UserData struct換成自己的請(qǐng)求數(shù)據(jù),把Dothing里面的代碼換成讓goroutine多任務(wù)執(zhí)行的代碼就可以在自己的項(xiàng)目中使用了。
PutData有請(qǐng)求數(shù)據(jù)就放入channel,每個(gè)goroutine不停的循環(huán)從channel里面取數(shù)據(jù),取到數(shù)據(jù)之后就執(zhí)行相應(yīng)的邏輯流程,可以看到整體的調(diào)度都是channel來(lái)控制的,通過(guò)channel的通信來(lái)傳遞數(shù)據(jù)。
不要通過(guò)共享內(nèi)存來(lái)通信,要通過(guò)通信來(lái)共享內(nèi)存
看看大概的代碼分析
- InitWorker的時(shí)候會(huì)創(chuàng)建queue個(gè)channl,再創(chuàng)建workers個(gè)goroutine,執(zhí)行g(shù)o Proc()
- Proc方法,里面有for的無(wú)限循環(huán),不停從步驟1里面創(chuàng)建的channl里面獲取UserData數(shù)據(jù),一旦獲取數(shù)據(jù)成功,就會(huì)帶著UserData數(shù)據(jù)去執(zhí)行Dothing方法。需要注意的是,這是workers個(gè)goroutine都在執(zhí)行Proc
- Dothing方法,就是讓某一個(gè)goroutine拿到UserData數(shù)據(jù)去處理數(shù)據(jù),執(zhí)行邏輯
- Close方法,給所有的goroutine發(fā)送關(guān)閉的信號(hào),channl里面不在有數(shù)據(jù)寫入,waiter.Wait()等待現(xiàn)有的channel里面數(shù)據(jù)被消費(fèi)完,goroutine就執(zhí)行完畢退出。
- PutData方法,就是把請(qǐng)求的數(shù)據(jù)交給goroutine去執(zhí)行。具體的做法,是把數(shù)據(jù) 塞到channl隊(duì)列里面,如果queue個(gè)channl隊(duì)列已滿,就拋出溢出錯(cuò)誤。
當(dāng)然了PutData也可以等待channl隊(duì)列里面的數(shù)據(jù)被Proc拿出,然后空出位置再塞數(shù)據(jù)到channl隊(duì)列。
func (this *ServerLogic) PutData(user UserData) error { timer := time.NewTimer(3*time.Second) select { case this.ch<-user: return nil case <-timer.C: return fmt.Errorf("put timeout") } }
加一個(gè)超時(shí)器,總不能等到天荒地老把,如果超過(guò)三秒,仍然沒(méi)有空出channl位置,現(xiàn)有的隊(duì)列還沒(méi)有消費(fèi)完,就拋出塞數(shù)據(jù)超時(shí)的錯(cuò)誤.
看一下樣例的使用的代碼
package main import ( context2 "context" "fmt" "test/logic" ) func main() { context := context2.Background() server := logic.NewServerLogic(&context, 1, 2) rt1 := server.PutData(logic.UserData{ Age: 11, Name: "test1", Postion: "golang", }) fmt.Println(rt1) rt2 := server.PutData(logic.UserData{ Age: 12, Name: "test2", Postion: "golang", }) fmt.Println(rt2) rt3 := server.PutData(logic.UserData{ Age: 13, Name: "test3", Postion: "golang", }) fmt.Println(rt3) server.Close() fmt.Println("end") }
等待了大概三十多秒之后的結(jié)果,打印結(jié)果其實(shí)跟預(yù)想的是一樣的。
<nil>
<nil>
queue overflow
end
NewServerLogic(&context, 1, 2)代碼中,我們要求創(chuàng)建了1個(gè)goroutine,大小為2的channl隊(duì)列。
所以第一個(gè)PutData和第二個(gè)PutData是塞數(shù)據(jù)成功的。等到第三次PutData的時(shí)候,因?yàn)槲覀僣hannl隊(duì)列的大小是2,已經(jīng)被占滿了,所以第三次就會(huì)提示溢出錯(cuò)誤。
使用goroutine另一種方法
我看項(xiàng)目中還有一些其他人的使用方法,區(qū)別只是退出的時(shí)候沒(méi)有使用context的cancel方法,而是使用了channel去通知退出goroutine,內(nèi)部的原理其實(shí)是一樣的??匆幌孪旅娴拇a。
func(this *ServerLogic)InitWorker(workers int, queue int) { this.quit = make(chan bool) this.ch = make(chan UserData, queue) this.waiter.Add(workers) for i :=0;i< workers; i++ { go this.Proc() } } func(this *ServerLogic)Proc() { defer this.waiter.Done() for { select { case t := ←this.ch: this.Dothing(t) case ←this.quit: return } } } func(this *ServerLogic)Close(){ close(this.quit) this.waiter.Wait() }
只有關(guān)閉這里是不一樣的,其他的基本一致。執(zhí)行退出的時(shí)候在Close()方法中,close(this.quit)會(huì)給quit channel寫入數(shù)據(jù),Proc()方法會(huì)循環(huán)從channel和quit里面取數(shù)據(jù),一旦從this.quit里面取出了數(shù)據(jù),說(shuō)明系統(tǒng)讓關(guān)閉goroutine,然后Proc方法就終止。
go func()行不行
有人說(shuō),扯這么多,為啥go func()不行,我在項(xiàng)目里面使用go func()運(yùn)行的好好,而且golang的HTTP庫(kù)里也是使用的go c.serve(ctx)。
我的理解是主要看使用場(chǎng)景,如果你的服務(wù)對(duì)結(jié)果要求不是100%的成功,對(duì)并發(fā)的要求很高,那就可以使用go func(),go c.serve(ctx)也是類似,TCP本身就是不可靠的連接,HTTP也允許有極少量的失敗狀態(tài)。
如果你的服務(wù)里面只是想讓多個(gè)goroutine處理你的數(shù)據(jù),不希望這個(gè)goroutine太多影響你的主干服務(wù),或者你為了提高數(shù)據(jù)處理效率,想讓多個(gè)goroutine去請(qǐng)求第三方的服務(wù),這樣的話,就應(yīng)該創(chuàng)建若干個(gè)goroutine去并發(fā)處理你的任務(wù),也不建議直接go func(),goroutine數(shù)量不可控,會(huì)影響其他的主干服務(wù)或者占用服務(wù)器資源,如果請(qǐng)求第三方的服務(wù),可能會(huì)因?yàn)椴l(fā)太高被限制,或者把第三方服務(wù)打掛。我們就遇到過(guò)這種情況。
總之,使用場(chǎng)景很重要,不是一概而論的。
到此這篇關(guān)于淺析Golang開(kāi)發(fā)中g(shù)oroutine的正確使用姿勢(shì) 的文章就介紹到這了,更多相關(guān)Go goroutine內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家
相關(guān)文章
關(guān)于go平滑重啟庫(kù)overseer實(shí)現(xiàn)原理詳解
這篇文章主要為大家詳細(xì)介紹了關(guān)于go平滑重啟庫(kù)overseer實(shí)現(xiàn)原理,文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,有需要的小伙伴可以參考下2023-11-11golang構(gòu)建工具M(jìn)akefile使用詳解
這篇文章主要為大家介紹了golang構(gòu)建工具M(jìn)akefile的使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07go語(yǔ)言中字符串嵌套的幾種實(shí)現(xiàn)方式
在Go語(yǔ)言中,字符串嵌套可以通過(guò)多種方式實(shí)現(xiàn),包括使用雙引號(hào)和轉(zhuǎn)義字符、反引號(hào)、字符串拼接和格式化字符串,下面就來(lái)介紹一下,感興趣的可以了解一下2025-03-03源碼解析gtoken替換jwt實(shí)現(xiàn)sso登錄
這篇文章主要為大家介紹了源碼解析gtoken替換jwt實(shí)現(xiàn)sso登錄的示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06如何組織Go代碼目錄結(jié)構(gòu)依賴注入wire使用解析
這篇文章主要為大家介紹了如何組織Go代碼目錄結(jié)構(gòu)依賴注入wire使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07使用Go語(yǔ)言構(gòu)建高效的二叉搜索樹(shù)聯(lián)系簿
樹(shù)是一種重要的數(shù)據(jù)結(jié)構(gòu),而二叉搜索樹(shù)(BST)則是樹(shù)的一種常見(jiàn)形式,在本文中,我們將學(xué)習(xí)如何構(gòu)建一個(gè)高效的二叉搜索樹(shù)聯(lián)系簿,感興趣的可以了解下2024-01-01Golang標(biāo)準(zhǔn)庫(kù)container/list的用法圖文詳解
提到單向鏈表,大家應(yīng)該是比較熟悉的了,這篇文章主要為大家詳細(xì)介紹了Golang標(biāo)準(zhǔn)庫(kù)container/list的用法相關(guān)知識(shí),感興趣的小伙伴可以了解下2024-01-01