原文: https://www.jianshu.com/p/147bd63801b6安全
--------------------------------------併發
Go與其餘語言不同,它從語言層面就已經支持併發,不須要咱們依託Thread庫新建線程。Go中的channel機制使咱們不用過多考慮鎖和併發安全問題。channel提供了一種goroutine之間數據流傳輸的方式。函數
今天我想從一個常見的deadlock error開始,討論一下channel的特性。ui
若是運行如下程序:spa
var ch = make(chan int) func main() { ch <- 1 <-ch // 沒有這行代碼也會報一樣的錯誤 }
terminal會報以下錯誤:.net
fatal error: all goroutines are asleep - deadlock!
回顧channel(信道)的概念,大體上來講,信道是goroutine之間相互溝通的管道,信道中數據的流通表明着goroutine之間內存的共享。宏觀上來說,信道有點像其餘語言中的隊列(queue),遵循先進先出的規則。線程
信道分爲無緩衝信道(即unbuffered channel)和有緩衝信道(buffered channel)。對於無緩衝的信道來講,咱們默認信道的發消息(send)和收消息(receive)都是阻塞(block)的。換句話來講,無緩衝的信道在收消息和發消息的時候,goroutine都處於掛起狀態。除非另外一端準備好,不然goroutine沒法繼續往下執行。code
上面的那段程序即是一個明顯的錯誤樣例。在main函數執行到ch <- 1的時候main(也是一個goroutine)便已掛起,而並無其餘goroutine負責接收消息,而下面一句 <-ch 永遠沒法執行,系統便自動判爲timeout返回error。這種全部線程或者進程都在等待資源釋放的狀況,咱們便把它稱之爲死鎖。blog
死鎖是一個很是有意思的話題,常見的死鎖大體分爲如下幾類:
i. 只在單一goroutine裏操做信道,例子如上。
ii. 串聯信道中間一環掛起,舉例以下:隊列
var ch1 chan int = make(chan int) var ch2 chan int = make(chan int) func say(s string) { fmt.Println(s) ch1 <- <- ch2 // ch1 等待 ch2流出的數據 } func main() { go say("hello") <- ch1 // 堵塞主線 }
ch1等待ch2留出數據,然而ch2並無發出數據致使goroutine阻塞,解決方案是給ch2喂數據:
func feedCh2(ch chan int) { ch <- 2 }
iii. 非緩衝信道不成對出現:
c, quit := make(chan int), make(chan int) go func() { c <- 1 // c通道的數據沒有被其餘goroutine讀取走,堵塞當前goroutine quit <- 0 // quit始終沒有辦法寫入數據 }() <- quit // quit 等待數據的寫
固然,並不是全部不成對出現的非緩衝信道都會報錯:
func say(ch chan int) { ch <- 1 } func main() { ch := make(chan int) go say(ch) }
有意思的是,雖然say函數掛起等待信道接收消息,可是main goroutine並無被阻塞,在main函數返回後程序依然能夠自動終止。
關於緩衝信道將會在以後的文章中介紹,若有意見還請指教。
Reference: http://blog.csdn.net/kjfcpua/article/details/18265441