鏈客,專爲開發者而生,有問必答!程序員
此文章來自區塊鏈技術社區,未經容許拒絕轉載。安全
Go的Channel是一個很強大的併發數據模型,在一個發送者和多個消費者狀況下工做得最好,可是若是是多個發送者,那麼在Channel關閉時須要協調多個發送者,等待它們發送消費完畢,同時也會致使一個Channel屢次關閉的狀況,這個問題Go社區已經注意到,並正在試圖解決:issue#14601服務器
讓咱們看看來自網絡
go channels are bad and you should feel bad | jtol一文是如何抱怨Channel有多很差,如下是原文大意轉述:數據結構
首先,不用質疑的是,該文博主自從2010中期就開始使用Go語言,參與編寫了425K行的純Go代碼,併爲Go社區提供了很多開源項目。併發
Go語言是以Channel和Goroutine(輕量線程)著稱,它的理論模型主要是基於CSP模型(Communicating Sequential Processes),可是該文博主認爲,從程序員角度看,Go的Channel模型實現方式是錯誤的,它是一個完全的反模式。函數
CSP模型是一種高併發計算模型,這種模型自己是試圖經過一個通道同步管理髮送和接受,可是,若是一旦你引入其餘同步機制如互斥鎖mutex、semaphore或條件判斷變量,你就不再是純的CSP了。高併發
下面讓咱們看看使用GO編寫CSP模型,這個案例是關於如何接受到選手的最高分。性能
首先,使用Game做爲數據結構:區塊鏈
type Game struct {
bestScore int
scores chan int
}
<p>
這裏狀態變量bestScore 並不使用互斥鎖進行保護,由於咱們只有一個goroutine管理這個狀態,新的分數是經過一個channel接受的。
func (g *Game) run() {
for score := range g.scores {
if g.bestScore < score { g.bestScore = score }
}
}
<p>
如今開始一個啓動game的構造器:
func NewGame() (g *Game) {
g = &Game{
bestScore: 0, scores: make(chan int),
}
go g.run()
return g
}
<p>
下一步,讓咱們假設有人給咱們一個Player接口,這個接口能夠得到分數score,也可能返回錯誤,由於也許網絡鏈接錯誤或player已經退出。
type Player interface {
NextScore() (score int, err error)
}
<p>
下面是處理player,首先檢查錯誤,而後將接受到的分數發給channel。
func (g *Game) HandlePlayer(p Player) error {
for {
score, err := p.NextScore() if err != nil { return err } g.scores <- score
}
}
<p>
好了,咱們有一個Game類型,可以以線程安全方式跟蹤Player的最高分。
當你將這個遊戲上線運行後,你得到了很是地成功,你的遊戲服務器上運行了不少這樣的Game程序。可是你會發現人們有時會離開你的遊戲,許多遊戲裏面沒有任何玩家,可是遊戲自己沒有中止而是不停地在循環運行,你被(*Game).run協程搞得不知所措了。
其實這個案例徹底能夠不使用Channel簡單地完成:
type Game struct {
mtx sync.Mutex
bestScore int
}
func NewGame() *Game {
return &Game{}
}
func (g *Game) HandlePlayer(p Player) error {
for {
score, err := p.NextScore() if err != nil { return err } g.mtx.Lock() if g.bestScore < score { g.bestScore = score } g.mtx.Unlock()
}
}
<p>
這裏只是引入了互斥鎖,替代了原來的Channel方式。使用傳統的同步鎖替代CSP的Go channel竟然解決了問題。
那麼,讓咱們看看Go語言的Channel背後是什麼?Channel是使用鎖保證連續訪問的線程安全性,使用Channel同步化內存的訪問,其實你是在使用鎖(banq注:這違背了CSP模型自己定義,CSP存在的前提就是避免使用鎖。),鎖被線程安全的隊列給包裝了,那麼Go的這個神祕的鎖與直接使用標準庫中mutex有什麼區別?下面是二者性能測試結果:
BenchmarkSimpleSet-8 3000000 391 ns/op
BenchmarkSimpleChannelSet-8 1000000 1699 ns/op
<p>
對於unbuffered channel也是一樣測試結果。(測試結果代表:Channel性能比mutex性能要差得多)
也許Go的調度器會提升,可是這意味着老的舊的互斥鎖和條件變量表現得很是好,更有效,更快速。若是你須要性能,那麼就使用這些真正的方法(互斥鎖等方法)。
此後,該文做者還指出Channel其實並不能和其餘併發原始模型很好地組合在一塊兒。好比Channel和互斥鎖放在一塊兒就帶來不肯定性。
他還指出,在API中使用Channel反而使得問題變得糟糕,你徹底能夠在沒有任何協程狀況下使用互斥鎖mutexe, semaphores,和回調callbacks編寫API,有時,回調函數反而更增強大,雖然,goroutine協程口口聲聲說是用來替代萬惡的回調函數的。協程只是比線程較爲輕量,可是較爲輕量不表明它是最輕量的。
他還談到:對一個已經關閉的Channel再進行關閉或發送操做是很是痛苦,若是你要關閉一個Channel,須要將關閉狀態進行同步化操做,使用mutex互斥鎖等,可是它們又不能很好地協調在一塊兒,爲什須要有關閉的狀態?由於這樣纔會防止其餘寫入者同時寫入或關閉一個已經關閉的channel。