這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Go與其他語言不一樣,它從語言層面就已經支援並發,不需要我們依託Thread庫建立線程。Go中的channel機制使我們不用過多考慮鎖和並發安全問題。channel提供了一種goroutine之間資料流傳輸的方式。
今天我想從一個常見的deadlock error開始,討論一下channel的特性。
如果運行以下程式:
var ch = make(chan int)func main() { ch <- 1 <-ch // 沒有這行代碼也會報同樣的錯誤}
terminal會報如下錯誤:
fatal error: all goroutines are asleep - deadlock!
回顧channel(通道)的概念,大致上來說,通道是goroutine之間相互溝通的管道,通道中資料的流通代表著goroutine之間記憶體的共用。宏觀上來講,通道有點像其他語言中的隊列(queue),遵循先進先出的規則。
通道分為無緩衝通道(即unbuffered channel)和有緩衝通道(buffered channel)。對於無緩衝的通道來說,我們預設通道的發訊息(send)和收訊息(receive)都是阻塞(block)的。換句話來說,無緩衝的通道在收訊息和發訊息的時候,goroutine都處於掛起狀態。除非另一端準備好,否則goroutine無法繼續往下執行。
上面的那段程式便是一個明顯的錯誤範例。在main函數執行到ch <- 1的時候main(也是一個goroutine)便已掛起,而並沒有其他goroutine負責接收訊息,而下面一句 <-ch 永遠無法執行,系統便自動判為timeout返回error。這種所有線程或者進程都在等待資源釋放的情況,我們便把它稱之為死結。
死結是一個非常有意思的話題,常見的死結大致分為以下幾類:
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
-完-