這篇文章將為大家詳細(xì)講解有關(guān)golang中g(shù)oroutine的使用方法,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。
成都創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供靖安網(wǎng)站建設(shè)、靖安做網(wǎng)站、靖安網(wǎng)站設(shè)計(jì)、靖安網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)與制作、靖安企業(yè)網(wǎng)站模板建站服務(wù),十年靖安做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
go中的goroutine是go語(yǔ)言在語(yǔ)言級(jí)別支持并發(fā)的一種特性。初接觸go的時(shí)候?qū)o的goroutine的歡喜至極,實(shí)現(xiàn)并發(fā)簡(jiǎn)便到簡(jiǎn)直bt的地步。
但是在項(xiàng)目過(guò)程中,越來(lái)越發(fā)現(xiàn)goroutine是一個(gè)很容易被大家濫用的東西。goroutine是一把雙面刃。這里列舉一下goroutine使用的幾宗罪:
1、goroutine的指針傳遞是不安全的
fun main() { request := request.NewRequest() //這里的NewRequest()是傳遞回一個(gè)type Request的指針 go saveRequestToredis1(request) go saveReuqestToRedis2(request) select{} }
非常符合邏輯的代碼:
主routine開(kāi)一個(gè)routine把request傳遞給saveRequestToRedis1,讓它把請(qǐng)求儲(chǔ)存到redis節(jié)點(diǎn)1中
同時(shí)開(kāi)另一個(gè)routine把request傳遞給saveReuqestToRedis2,讓它把請(qǐng)求儲(chǔ)存到redis節(jié)點(diǎn)2中
然后主routine就進(jìn)入循環(huán)(不結(jié)束進(jìn)程)
問(wèn)題現(xiàn)在來(lái)了,saveRequestToRedis1和saveReuqestToRedis2兩個(gè)函數(shù)其實(shí)不是我寫的,而是團(tuán)隊(duì)另一個(gè)人寫的,我對(duì)其中的實(shí)現(xiàn)一無(wú)所知,也不想去仔細(xì)看內(nèi)部的具體實(shí)現(xiàn)。但是根據(jù)函數(shù)名,我想當(dāng)然地把request指針傳遞進(jìn)入。
實(shí)際上saveRequestToRedis1和saveRequestToRedis2 是這樣實(shí)現(xiàn)的:
func saveRequestToRedis1(request *Request){ … request.ToUsers = []int{1,2,3} //這里是一個(gè)賦值操作,修改了request指向的數(shù)據(jù)結(jié)構(gòu) … redis.Save(request) return }
這樣有什么問(wèn)題?saveRequestToRedis1和saveReuqestToRedis2兩個(gè)goroutine修改了同一個(gè)共享數(shù)據(jù)結(jié)構(gòu),但是由于routine的執(zhí)行是無(wú)序的,因此我們無(wú)法保證request.ToUsers設(shè)置和redis.Save()是一個(gè)原子操作,這樣就會(huì)出現(xiàn)實(shí)際存儲(chǔ)redis的數(shù)據(jù)錯(cuò)誤的bug。
好吧,你可以說(shuō)這個(gè)saveRequestToRedis的函數(shù)實(shí)現(xiàn)的有問(wèn)題,沒(méi)有考慮到會(huì)是使用go routine調(diào)用。請(qǐng)?jiān)傧胍幌?,這個(gè)saveRequestToRedis的具體實(shí)現(xiàn)是沒(méi)有任何問(wèn)題的,它不應(yīng)該考慮上層是怎么使用它的。
那就是我的goroutine的使用有問(wèn)題,主routine在開(kāi)一個(gè)routine的時(shí)候并沒(méi)有確認(rèn)這個(gè)routine里面的任何一句代碼有沒(méi)有修改了主routine中的數(shù)據(jù)。對(duì)的,主routine確實(shí)需要考慮這個(gè)情況。
主goroutine在啟用go routine的時(shí)候需要閱讀子routine中的每行代碼來(lái)確定是否有修改共享數(shù)據(jù)??這在實(shí)際項(xiàng)目開(kāi)發(fā)過(guò)程中是多么降低開(kāi)發(fā)速度的一件事情??!
go語(yǔ)言使用goroutine是想減輕并發(fā)的開(kāi)發(fā)壓力,卻不曾想是在另一方面增加了開(kāi)發(fā)壓力。
上面說(shuō)的那么多,就是想得出一個(gè)結(jié)論:
gorotine的指針傳遞是不安全的??!
如果上一個(gè)例子還不夠隱蔽,這里還有一個(gè)例子:
fun (this *Request)SaveRedis() { redis1 := redis.NewRedisAddr("xxxxxx") redis2 := redis.NewRedisAddr("xxxxxx") go this.saveRequestToRedis(redis1) go this.saveRequestToRedis(redis2) select{} }
很少人會(huì)考慮到this指針指向的對(duì)象是否會(huì)有問(wèn)題,這里的this指針傳遞給routine應(yīng)該說(shuō)是非常隱蔽的。
2、goroutine增加了函數(shù)的危險(xiǎn)系數(shù)
這點(diǎn)其實(shí)也是源自于上面一點(diǎn)。上文說(shuō),往一個(gè)go函數(shù)中傳遞指針是不安全的。那么換個(gè)角度想,你怎么能保證你要調(diào)用的函數(shù)在函數(shù)實(shí)現(xiàn)內(nèi)部不會(huì)使用go呢?如果不去看函數(shù)體內(nèi)部具體實(shí)現(xiàn),是沒(méi)有辦法確定的。
例如我們將上面的典型例子稍微改改
func main() { request := request.NewRequest() saveRequestToRedis1(request) saveRequestToRedis2(request) select{} }
這下我們沒(méi)有使用并發(fā),就一定不會(huì)出現(xiàn)這問(wèn)題了吧?追到函數(shù)里面去,傻眼了:
func saveReqeustToRedis1(request *Request) { … go func() { … request.ToUsers = []{1,2,3} …. redis.Save(request) } }
里面起了一個(gè)goroutine,并修改了request指針指向的對(duì)象。這里就產(chǎn)生了錯(cuò)誤了。好吧,如果在調(diào)用函數(shù)的時(shí)候,不看函數(shù)內(nèi)部的具體實(shí)現(xiàn),這個(gè)問(wèn)題就無(wú)法避免。
所以說(shuō),從最壞的思考角度出發(fā),每個(gè)調(diào)用函數(shù)理論上來(lái)說(shuō)都是不安全的!試想一下,這個(gè)調(diào)用函數(shù)如果不是自己開(kāi)發(fā)組的人編寫的,而是使用網(wǎng)絡(luò)上的第三方開(kāi)源代碼...確實(shí)無(wú)法想象找出這個(gè)bug要花費(fèi)多少時(shí)間。
3、goroutine的濫用陷阱
看一下這個(gè)例子:
func main() { go saveRequestToRedises(request) } func saveRequestToRedieses(request *Request) { for _, redis := range Redises { go redis.saveRequestToRedis(request) } } func saveRequestToRedis(request *Request) { …. go func() { request.ToUsers = []{1,2,3} … redis.Save(request) } }
神奇啊,go無(wú)處不在,好像眨眨眼就在哪里冒出來(lái)了。這就是go的濫用,到處都見(jiàn)到go,但是卻不是很明確,哪里該用go?為什么用go?goroutine確實(shí)會(huì)有效率的提升么?
c語(yǔ)言的并發(fā)比go語(yǔ)言的并發(fā)復(fù)雜和繁瑣地多,因此我們?cè)谑褂弥皶?huì)深思,考慮使用并發(fā)獲得的好處和壞處。
處理方法
下面說(shuō)幾個(gè)我處理這些問(wèn)題的方法:
1、當(dāng)啟動(dòng)一個(gè)goroutine的時(shí)候,如果一個(gè)函數(shù)必須要傳遞一個(gè)指針,但是函數(shù)層級(jí)很深,在無(wú)法保證安全的情況下,傳遞這個(gè)指針指向?qū)ο蟮囊粋€(gè)克隆,而不是直接傳遞指針
fun main() { request := request.NewRequest() go saveRequestToRedis1(request.Clone()) go saveReuqestToRedis2(request.Clone()) select{} }
Clone函數(shù)需要另外寫??梢栽诮Y(jié)構(gòu)體定義之后簡(jiǎn)單跟上這個(gè)方法。比如:
func (this *Request)Clone(){ newRequest := NewRequst() newRequest.ToUsers = make([]int, len(this.ToUsers)) copy(newRequest.ToUsers, this.ToUsers) }
其實(shí)從效率角度考慮這樣確實(shí)會(huì)產(chǎn)生不必要的Clone的操作,耗費(fèi)一定內(nèi)存和CPU。但是在我看來(lái),首先,為了安全性,這個(gè)嘗試是值得的。
其次,如果項(xiàng)目對(duì)效率確實(shí)有很高的要求,那么你不妨在開(kāi)發(fā)階段遵照這個(gè)原則使用clone,然后在項(xiàng)目?jī)?yōu)化階段,作為一種優(yōu)化手段,將不必要的Clone操作去掉。這樣就能在保證安全的前提下做到最好的優(yōu)化。
2、什么時(shí)候使用go的問(wèn)題
有兩種思維邏輯會(huì)想到使用goroutine:
1 業(yè)務(wù)邏輯需要并發(fā)
比如一個(gè)服務(wù)器,接收請(qǐng)求,阻塞式的方法是一個(gè)請(qǐng)求處理完成后,才開(kāi)始第二個(gè)請(qǐng)求的處理。其實(shí)在設(shè)計(jì)的時(shí)候我們一定不會(huì)這么做,我們會(huì)在一開(kāi)始就已經(jīng)想到使用并發(fā)來(lái)處理這個(gè)場(chǎng)景,每個(gè)請(qǐng)求啟動(dòng)一個(gè)goroutine為它服務(wù),這樣就達(dá)到了并行的效果。這種goroutine直接按照思維的邏輯來(lái)使用goroutine
2 性能優(yōu)化需要并發(fā)
一個(gè)場(chǎng)景是這樣:需要給一批用戶發(fā)送消息,正常邏輯會(huì)使用
for _, user := range users { sendMessage(user) }
但是在考慮到性能問(wèn)題的時(shí)候,我們就不會(huì)這樣做,如果users的個(gè)數(shù)很大,比如有1000萬(wàn)個(gè)用戶?我們就沒(méi)必要將1000萬(wàn)個(gè)用戶放在一個(gè)routine中運(yùn)行處理,考慮將1000萬(wàn)用戶分成1000份,每份開(kāi)一個(gè)goroutine,一個(gè)goroutine分發(fā)1萬(wàn)個(gè)用戶,這樣在效率上會(huì)提升很多。這種是性能優(yōu)化上對(duì)goroutine的需求
按照項(xiàng)目開(kāi)發(fā)的流程角度來(lái)看。在項(xiàng)目開(kāi)發(fā)階段,第一種思路的代碼實(shí)現(xiàn)會(huì)直接影響到后續(xù)的開(kāi)發(fā)實(shí)現(xiàn),因此在項(xiàng)目開(kāi)發(fā)階段應(yīng)該馬上實(shí)現(xiàn)。
但是第二種,項(xiàng)目中是由很多小角落是可以使用goroutine進(jìn)行優(yōu)化的,但是如果在開(kāi)發(fā)階段對(duì)每個(gè)優(yōu)化策略都考慮到,那一定會(huì)直接打亂你的開(kāi)發(fā)思路,會(huì)讓你的開(kāi)發(fā)周期延長(zhǎng),而且很容易埋下潛在的不安全代碼。
因此第二種情況在開(kāi)發(fā)階段絕不應(yīng)該直接使用goroutine,而該在項(xiàng)目?jī)?yōu)化階段以優(yōu)化的思路對(duì)項(xiàng)目進(jìn)行重構(gòu)。
關(guān)于golang中g(shù)oroutine的使用方法就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。
當(dāng)前名稱:golang中g(shù)oroutine的使用方法
標(biāo)題來(lái)源:http://www.rwnh.cn/article44/jipgee.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信小程序、域名注冊(cè)、服務(wù)器托管、手機(jī)網(wǎng)站建設(shè)、軟件開(kāi)發(fā)、移動(dòng)網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)