[go語言]channel的一個“奇怪”特性

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

go語言的channel有一個看上去很奇怪的特性,就是如果向一個為空白值(nil)的channel寫入或者讀取資料,當前goroutine將永遠阻塞。例如

func main() {var ch chan intch <- 1   // block forerver}func main() {var ch chan int<-ch   // block forerver}func main() {<-chan int(nil)   // block forerver}func main() {chan int(nil)<-1   // block forerver}

以上四個main函數都會永遠阻塞(但是因為沒有其他goroutine,所以runtime會報告一個deadlock錯誤)。

這看上去似乎是一個bug,因為向一個沒有初始化的channel讀或者寫其實是沒有意義的。但是為什麼go team要這麼設計呢?

關於這個問題在golang-nuts上有很多討論,其中有一組討論[1]說到其實這個特性(即對nil channel的讀寫永遠阻塞)可以用來比較優雅地實現一個叫做"guarded selective wating"的模式,其實就是條件等待:在select中的一些case中如果對應的條件不滿足就不在這個case上等待。假設有這樣一個條件等待的需求:

select {case <-chan_a: // 希望cond_a為真時才在chan_a上等待// do somethingcase <-chan_b: // 希望cond_b為真時才在chan_b上等待// do somethingcase <-chan_def:// do something}

這個模式如果不利用這個特性,也是可以實現的,但是代碼就比較冗長難看。其中一種實現可能是這樣:

switch {case cond_a && cond_b:select {case <-chan_a:// do somethingcase <-chan_b:// do somethingcase <-chan_def:// do something}case !cond_a && cond_b:select {case <-chan_b:// do somethingcase <-chan_def:// do something}case cond_a && !cond_b:select {case <-chan_a:// do somethingcase <-chan_def:// do something}default:select {case <-chan_def:// do something}}

可以看到,實現代碼非常的冗長羅嗦容易出錯。而且如果case分支更多一些,實現代碼的行數會以2的指數的數量爆炸性增長。當然還有其他實現方式,但如果不想辦法去故意阻塞一個channel,實現的方法都是大同小異,都有前面說的問題。

但是如果用了nil channel特性,實現起來就可以非常的優雅簡潔:

maybe := func(flag bool, ch chan int) <-chan int {if flag {return ch}return nil}select {case <- maybe(cond_a, chan_a):// do somethingcase <- maybe(cond_b, chan_b):// do somethingcase <- chan_def:// do something}

這裡實際是利用了nil channel永遠阻塞的特性,但是如果我們建立一個channel,但是不向它寫資料也不關閉它,而是只從它讀資料,那麼也是可以實現永遠阻塞的。以下代碼實現了同樣的效果:

var _BLOCK = make(<-chan int)maybe := func(flag bool, ch chan int) <-chan int {if flag {return ch}return _BLOCK}select {case <- maybe(cond_a, chan_a):// do somethingcase <- maybe(cond_b, chan_b):// do somethingcase <- chan_def:// do something}

這也就意味著:對於實現一個"guarded selective wating"模式來說,nil channel的永久阻塞的特性並不是必須的,因為有其他替代實現方式。但是顯然用nil channel更方便,也不需要額外浪費資源去建立一個用來永久阻塞的channel。

一些爭議:

有人說就算nil channel在select裡很有用,但是在select之外單獨去讀寫一個nil channel確實是個很奇怪的行為,無論如何看上去都是一個bug。runtime如果在這裡產生一個panic而不是永久阻塞,就可以更好地告訴程式員說:嗨,你這裡有個bug。如果是永久阻塞的話,這個bug就不會那麼容易被注意到。

go team的成員回應說:這個是為了和在select裡的行為一致,如果nil channel在select裡永久阻塞而在其他地方panic,行為就不一致了,會讓程式員感到疑惑;而且這也違反了go1的語言規範;另外讀nil channel永久阻塞,和讀一個沒有資料的channel效果是一樣的,如同遍曆一個為空白值的數組切片或者map和遍曆一個長度為0的數組切片或者沒有成員的map效果也是一樣。

例如:
var s []int  // 未初始化,s是一個空值for k, v := range s {// do something}
s := []int{}  // 已初始化,s長度為0for k, v := range s {// do something}

這兩段程式碼為是一樣的,迴圈體裡的代碼都不會執行到,也都不會panic。

我的看法是在select之外的讀寫nil channel確實是一個bug,至少也是不好的代碼風格(如果真有人故意這麼用的話)。但是它並不容易在實際中出現,因為我們在使用channel的時候通常是把聲明和初始化放在一起的,所以不會是空值;或者channel作為struct的一個成員,聲明和初始化是分離的,但是一般也會有一個函數來初始化這個結構。所以在實際編碼中並不容易產生讀寫一個nil channel的bug,這不是一個嚴重的問題。

[1]https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/ChPxr_h8kUM
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.