這篇文章總結了channel的10種經常使用操做,以一個更高的視角看待channel,會給你們帶來對channel更全面的認識。golang
在介紹10種操做前,先簡要介紹下channel的使用場景、基本操做和注意事項。安全
把channel用在數據流動的地方:併發
channel存在3種狀態
:less
nil
channel可進行3種操做
:異步
把這3種操做和3種channel狀態能夠組合出9種狀況
:高併發
操做 | nil的channel | 正常channel | 已關閉channel |
---|---|---|---|
<- ch | 阻塞 | 成功或阻塞 | 讀到零值 |
ch <- | 阻塞 | 成功或阻塞 | panic |
close(ch) | panic | 成功 | panic |
對於nil通道的狀況,也並不是徹底遵循上表,有1個特殊場景:當nil
的通道在select
的某個case
中時,這個case會阻塞,但不會形成死鎖。oop
參考代碼請看:https://dave.cheney.net/2014/...spa
下面介紹使用channel的10種經常使用操做。.net
for-range
讀取channel,這樣既安全又便利,當channel關閉時,for循環會自動退出,無需主動監測channel是否關閉,能夠防止讀取已經關閉的channel,形成讀到數據爲通道所存儲的數據類型的零值。for x := range ch{ fmt.Println(x) }
_,ok
判斷channel是否關閉原理:讀已關閉的channel會獲得零值,若是不肯定channel,須要使用ok
進行檢測。ok的結果和含義:指針
true
:讀到數據,而且通道沒有關閉。false
:通道關閉,無數據讀到。if v, ok := <- ch; ok { fmt.Println(v) }
select
能夠同時監控多個通道的狀況,只處理未阻塞的case。當通道爲nil時,對應的case永遠爲阻塞,不管讀寫。特殊關注:普通狀況下,對nil的通道寫操做是要panic的。// 分配job時,若是收到關閉的通知則退出,不分配job func (h *Handler) handle(job *Job) { select { case h.jobCh<-job: return case <-h.stopCh: return } }
用法:
// 只有generator進行對outCh進行寫操做,返回聲明 // <-chan int,能夠防止其餘協程亂用此通道,形成隱藏bug func generator(int n) <-chan int { outCh := make(chan int) go func(){ for i:=0;i<n;i++{ outCh<-i } }() return outCh } // consumer只讀inCh的數據,聲明爲<-chan int // 能夠防止它向inCh寫數據 func consumer(inCh <-chan int) { for x := range inCh { fmt.Println(x) } }
// 無緩衝 ch1 := make(chan int) ch2 := make(chan int, 0) // 有緩衝 ch3 := make(chan int, 1)
func test() { inCh := generator(100) outCh := make(chan int, 10) // 使用5個`do`協程同時處理輸入數據 var wg sync.WaitGroup wg.Add(5) for i := 0; i < 5; i++ { go do(inCh, outCh, &wg) } go func() { wg.Wait() close(outCh) }() for r := range outCh { fmt.Println(r) } } func generator(n int) <-chan int { outCh := make(chan int) go func() { for i := 0; i < n; i++ { outCh <- i } close(outCh) }() return outCh } func do(inCh <-chan int, outCh chan<- int, wg *sync.WaitGroup) { for v := range inCh { outCh <- v * v } wg.Done() }
select
和time.After
,看操做和定時器哪一個先返回,處理先完成的,就達到了超時控制的效果func doWithTimeOut(timeout time.Duration) (int, error) { select { case ret := <-do(): return ret, nil case <-time.After(timeout): return 0, errors.New("timeout") } } func do() <-chan int { outCh := make(chan int) go func() { // do work }() return outCh }
func unBlockRead(ch chan int) (x int, err error) { select { case x = <-ch: return x, nil case <-time.After(time.Microsecond): return 0, errors.New("read time out") } } func unBlockWrite(ch chan int, x int) (err error) { select { case ch <- x: return nil case <-time.After(time.Microsecond): return errors.New("read time out") } }
注:time.After等待能夠替換爲default,則是channel阻塞時,當即返回的效果
close(ch)
關閉全部下游協程ch
的協程都會收到close(ch)
的信號func (h *Handler) Stop() { close(h.stopCh) // 可使用WaitGroup等待全部協程退出 } // 收到中止後,再也不處理請求 func (h *Handler) loop() error { for { select { case req := <-h.reqCh: go handle(req) case <-h.stopCh: return } } }
chan struct{}
做爲信號channel// 上例中的Handler.stopCh就是一個例子,stopCh並不須要傳遞任何數據 // 只是要給全部協程發送退出的信號 type Handler struct { stopCh chan struct{} reqCh chan *Request }
reqCh chan *Request // 好過 reqCh chan Request
package main import ( "fmt" "math/rand" "sync" "time" ) func main() { reqs := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // 存放結果的channel的channel outs := make(chan chan int, len(reqs)) var wg sync.WaitGroup wg.Add(len(reqs)) for _, x := range reqs { o := handle(&wg, x) outs <- o } go func() { wg.Wait() close(outs) }() // 讀取結果,結果有序 for o := range outs { fmt.Println(<-o) } } // handle 處理請求,耗時隨機模擬 func handle(wg *sync.WaitGroup, a int) chan int { out := make(chan int) go func() { time.Sleep(time.Duration(rand.Intn(3)) * time.Second) out <- a wg.Done() }() return out }
- 若是這篇文章對你有幫助,請點個贊/喜歡,感謝。
- 本文做者:大彬
- 若是喜歡本文,隨意轉載,但請保留此原文連接:http://lessisbetter.site/2019/01/20/golang-channel-all-usage/