介紹Golang併發的模型寫了幾篇了,但一直沒有以channel爲主題進行介紹,今天就給你們聊一聊channel,channel的基本使用很是簡單,想必你們都已瞭解,因此直接來個進階點的:介紹channel的阻塞狀況,以及給你一個必殺技,立馬解決阻塞問題,實用性高。git
不管是有緩存通道、無緩衝通道都存在阻塞的狀況。阻塞場景共4個,有緩存和無緩衝各2個。github
無緩衝通道的特色是,發送的數據須要被讀取後,發送纔會完成,它阻塞場景:golang
// 場景1 func ReadNoDataFromNoBufCh() { noBufCh := make(chan int) <-noBufCh fmt.Println("read from no buffer channel success") // Output: // fatal error: all goroutines are asleep - deadlock! } // 場景2 func WriteNoBufCh() { ch := make(chan int) ch <- 1 fmt.Println("write success no block") // Output: // fatal error: all goroutines are asleep - deadlock! }
注:示例代碼中的Output註釋表明函數的執行結果,每個函數都因爲阻塞在通道操做而沒法繼續向下執行,最後報了死鎖錯誤。緩存
有緩存通道的特色是,有緩存時能夠向通道中寫入數據後直接返回,緩存中有數據時能夠從通道中讀到數據直接返回,這時有緩存通道是不會阻塞的,它阻塞場景是:併發
// 場景1 func ReadNoDataFromBufCh() { bufCh := make(chan int, 1) <-bufCh fmt.Println("read from no buffer channel success") // Output: // fatal error: all goroutines are asleep - deadlock! } // 場景2 func WriteBufChButFull() { ch := make(chan int, 1) // make ch full ch <- 100 ch <- 1 fmt.Println("write success no block") // Output: // fatal error: all goroutines are asleep - deadlock! }
select是執行選擇操做的一個結構,它裏面有一組case語句,它會執行其中無阻塞的那一個,若是都阻塞了,那就等待其中一個不阻塞,進而繼續執行,它有一個default語句,該語句是永遠不會阻塞的,咱們能夠藉助它實現無阻塞的操做。若是不瞭解,不想多瞭解一下select能夠先看下這2篇文章:less
下面示例代碼是使用select修改後的無緩衝通道和有緩衝通道的讀寫,如下函數能夠直接經過main函數調用,其中的Ouput的註釋是運行結果,從結果能看出,在通道不可讀或者不可寫的時候,再也不阻塞等待,而是直接返回。函數
// 無緩衝通道讀 func ReadNoDataFromNoBufChWithSelect() { bufCh := make(chan int) if v, err := ReadWithSelect(bufCh); err != nil { fmt.Println(err) } else { fmt.Printf("read: %d\n", v) } // Output: // channel has no data } // 有緩衝通道讀 func ReadNoDataFromBufChWithSelect() { bufCh := make(chan int, 1) if v, err := ReadWithSelect(bufCh); err != nil { fmt.Println(err) } else { fmt.Printf("read: %d\n", v) } // Output: // channel has no data } // select結構實現通道讀 func ReadWithSelect(ch chan int) (x int, err error) { select { case x = <-ch: return x, nil default: return 0, errors.New("channel has no data") } } // 無緩衝通道寫 func WriteNoBufChWithSelect() { ch := make(chan int) if err := WriteChWithSelect(ch); err != nil { fmt.Println(err) } else { fmt.Println("write success") } // Output: // channel blocked, can not write } // 有緩衝通道寫 func WriteBufChButFullWithSelect() { ch := make(chan int, 1) // make ch full ch <- 100 if err := WriteChWithSelect(ch); err != nil { fmt.Println(err) } else { fmt.Println("write success") } // Output: // channel blocked, can not write } // select結構實現通道寫 func WriteChWithSelect(ch chan int) error { select { case ch <- 1: return nil default: return errors.New("channel blocked, can not write") } }
使用default實現的無阻塞通道阻塞有一個缺陷:當通道不可讀或寫的時候,會便可返回。實際場景,更多的需求是,咱們但願嘗試讀一會數據,或者嘗試寫一會數據,若是實在無法讀寫再返回,程序繼續作其它的事情。spa
使用定時器替代default能夠解決這個問題,給通道增長讀寫數據的容忍時間,若是500ms內沒法讀寫,就即刻返回。示例代碼修改一下會是這樣:code
func ReadWithSelect(ch chan int) (x int, err error) { timeout := time.NewTimer(time.Microsecond * 500) select { case x = <-ch: return x, nil case <-timeout.C: return 0, errors.New("read time out") } } func WriteChWithSelect(ch chan int) error { timeout := time.NewTimer(time.Microsecond * 500) select { case ch <- 1: return nil case <-timeout.C: return errors.New("write time out") } }
結果就會變成超時返回:協程
read time out write time out read time out write time out
本文全部示例源碼,及歷史文章、代碼都存儲在Github:https://github.com/Shitaibin/golang_step_by_step/tree/master/channel/unblock_channel
這篇文章了channel的阻塞狀況,以及解決阻塞的2種辦法:
但願這篇文章對你的channel讀寫有所啓發。
- 若是這篇文章對你有幫助,請點個贊/喜歡,感謝。
- 本文做者:大彬
- 若是喜歡本文,隨意轉載,但請保留此原文連接:http://lessisbetter.site/2018/11/03/Golang-channel-read-and-write-without-blocking/