Go 中的 Goroutine 和其他并發(fā)處理方案的對比
Go 語言讓使用 Goroutine 和通道變得非常有吸引力,作為在 Go 中進(jìn)行并發(fā)的主要方式,它們是被有意識的提出的。因此對于你所遇到的任何與并發(fā)相關(guān)的問題,它們都可能成為首選方案。但是我不確定它們是否適合于我遇到的所有問題,我仍在考慮其中的平衡點(diǎn)。
通道和 Goroutine 對于查詢共享狀態(tài)(或從共享狀態(tài)中獲取某些信息)這類問題看起來似乎并不完全契合。假設(shè)你想要記錄那些與服務(wù)端建立 TLS 通信失敗的 SMTP 客戶端的 IP,以便在 TLS 握手失敗的情況下,不再提供 TLS 通信(或至少在給定的時(shí)間段內(nèi)不提供)。大多數(shù)基于通道的解決方案都很直白:一個(gè)主 Goroutine 維護(hù)一個(gè) IP 集合,通過通道向主 Goroutine 發(fā)送一條消息來向其添加 IP。但是,如何詢問主 Goroutine 某個(gè) IP 是否已經(jīng)存在?問題的關(guān)鍵在于,無法在共享通道上收到來自主 Goroutine 的答復(fù),因?yàn)橹?Goroutine 無法專門答復(fù)你。
針對這個(gè)問題,目前我看到的基于通道的解決方案是將一個(gè)回復(fù)通道作為查詢消息的一部分一起發(fā)送給主 goroutine(通過共享通道發(fā)送)。但是這種方法的有個(gè)副作用,那就是通道的頻繁分配和釋放,每次請求都會對通道進(jìn)行分配、初始化、使用一次然后銷毀(我認(rèn)為這些通道必須通過垃圾回收機(jī)制回收,而不是在棧上分配和釋放)。另一種方案是提供一個(gè)由 sync 包中鎖或其他同步工具顯式保護(hù)的數(shù)據(jù)結(jié)構(gòu),這是更底層的解決方案,需要更多的管理操作,但是卻避免了通道的頻繁分配和釋放。
對于我將要編寫的大多數(shù) Go 程序來說,效率往往不是首要的關(guān)注點(diǎn),真正的問題是,如何使編寫更容易并且代碼更清晰。目前我沒有一個(gè)徹底的結(jié)論,但卻有一個(gè)初步的、并不完全是我所期待的那個(gè):如果要針對同一共享狀態(tài)處理不止一種查詢,那么基于鎖的方案會更容易些。
在面對多種類型的狀態(tài)查詢時(shí),通道方案的問題在于它需要很多我稱之為“類型官僚主義”的東西。因?yàn)橥ǖ朗菗碛蓄愋偷模詫τ诿糠N不同類型的答復(fù)都需要定義相應(yīng)的答復(fù)通道類型(顯式或隱式)。然后,基本上每個(gè)不同的查詢也都需要自己的類型,因?yàn)椴樵兿⒈仨毎愋突模┗貜?fù)通道。基于鎖的方案并不會使這些類型相關(guān)的瑣事消失,但會減輕它們帶來的痛苦,因?yàn)榇藭r(shí)查詢消息和答復(fù)只是函數(shù)的參數(shù)和返回值,因此不必將它們正式的定義為 Go 類型(struct)。實(shí)際上,即便需要進(jìn)行額外的手動加鎖,這對我來說已經(jīng)是很輕松了。
(可以通過各種手段將這些類型合并在一起從而減少通道方案中所需類型的數(shù)量,但是此時(shí)便開始失去類型的安全性,尤其是編譯時(shí)類型檢查。我喜歡 Go 中的編譯時(shí)類型檢查,因?yàn)樗鼤芸孔V的告訴我是否遇到了明顯的錯(cuò)誤,并且這也有助于加快重構(gòu)的速度。)
從某種意義上說,我認(rèn)為通道和 Goroutine 是 Turing tarpit 的一種形式,因?yàn)槿绻阕銐蚵斆鞯脑挘梢詫⑺鼈儜?yīng)用于所有問題。
(另一方面,有時(shí)通道是解決看似與它們無關(guān)的問題的絕佳方案[1],在看到該文章之前,我從未想過在詞法分析器中使用 Goroutine 和通道。)
Sidebar:我所采用的 Go 鎖模式
這不是我的原創(chuàng),而是來源于 Go blog 中的 Go maps 實(shí)戰(zhàn)[2]一文,如下所示:
// ipEnt 是共享數(shù)據(jù)結(jié)構(gòu)中的真實(shí)條目。
type?ipEnt?struct?{
??when??time.time
??count?int
}
// ipMap 是由讀寫鎖保護(hù)的共享數(shù)據(jù)結(jié)構(gòu)。
type?ipMap?struct?{
??sync.RWMutex
??ips?map[string]*ipEnt
}
var?notls?=?&ipMap{ips:?make(map[string]*ipEnt)}
// Add 方法用于外部調(diào)用對共享數(shù)據(jù)進(jìn)行操作,每次都會首先獲取鎖,隨后釋放它。
func?(i?*ipMap)?Add(ip?string)?{
??i.Lock()
??...?manipulate?i.ips?...
??i.Unlock()
}
使用方法來操作數(shù)據(jù)結(jié)構(gòu)感覺是最自然的方式,部分原因是由于操作和鎖定的約束條件緊密的耦合在一起。而我喜歡它純粹是因?yàn)樗鶐淼暮啙崟鴮懛绞剑?/p>
if?res?==?TLSERROR?{
??notls.Add(remoteip)
??....
}
最后這點(diǎn)只是個(gè)人喜好,當(dāng)然,也有人更喜歡將 ipMap 作為參數(shù)傳遞給獨(dú)立的函數(shù)。
via: https://utcc.utoronto.ca/~cks/space/blog/programming/GoGoroutinesVsLocks
作者:ChrisSiebenmann[3]譯者:anxk[4]校對:polaris1119[5]
本文由 GCTT[6] 原創(chuàng)編譯,Go 中文網(wǎng)[7]
參考資料
有時(shí)通道是解決看似與它們無關(guān)的問題的絕佳方案: http://blog.golang.org/two-go-talks-lexical-scanning-in-go-and
[2]Go maps 實(shí)戰(zhàn): http://blog.golang.org/go-maps-in-action
[3]ChrisSiebenmann: https://utcc.utoronto.ca/~cks/space/People/ChrisSiebenmann
[4]anxk: https://github.com/anxk
[5]polaris1119: https://github.com/polaris1119
[6]GCTT: https://github.com/studygolang/GCTT
[7]Go 中文網(wǎng): https://studygolang.com/
推薦閱讀

