Go語言的Channel文章,整我的都感受很差了

鏈客,專爲開發者而生,有問必答!程序員

此文章來自區塊鏈技術社區,未經容許拒絕轉載。安全

圖片描述

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。

相關文章
相關標籤/搜索