新四季網

channel為什麼叫channel(原來channel有這麼多用法)

2023-10-18 10:17:51

這篇文章總結了channel的11種常用操作,以一個更高的視角看待channel,會給大家帶來對channel更全面的認識。

在介紹11種操作前,先簡要介紹下channel的使用場景、基本操作和注意事項。

channel的使用場景

把channel用在數據流動的地方

消息傳遞、消息過濾信號廣播事件訂閱與廣播請求、響應轉發任務分髮結果匯總並發控制同步與異步…channel的基本操作和注意事項

channel存在3種狀態:

nil,未初始化的狀態,只進行了聲明,或者手動賦值為nilactive,正常的channel,可讀或者可寫closed,已關閉,千萬不要誤認為關閉channel後,channel的值是nil

channel可進行3種操作:

讀寫關閉

把這3種操作和3種channel狀態可以組合出9種情況:

操作nil的channel正常channel已關閉channel<- ch阻塞成功或阻塞讀到零值ch <-阻塞成功或阻塞panicclose(ch)panic成功panic

對於nil通道的情況,也並非完全遵循上表,有1個特殊場景:當nil的通道在select的某個case中時,這個case會阻塞,但不會造成死鎖。

參考代碼請看:https://dave.cheney.net/2014/03/19/channel-axioms

下面介紹使用channel的10種常用操作。

1. 使用for range讀channel

場景

當需要不斷從channel讀取數據時。

原理

使用for-range讀取channel,這樣既安全又便利,當channel關閉時,for循環會自動退出,無需主動監測channel是否關閉,可以防止讀取已經關閉的channel,造成讀到數據為通道所存儲的數據類型的零值。

用法

for x := range ch{ fmt.Println(x)}

2. 使用v,ok := <-ch select操作判斷channel是否關閉

場景

v,ok := <-ch select操作判斷channel是否關閉

原理

ok的結果和含義:

- `true`:讀到通道數據,不確定是否關閉,可能channel還有保存的數據,但channel已關閉。- `false`:通道關閉,無數據讀到。

從關閉的channel讀值讀到是channel所傳遞數據類型的零值,這個零值有可能是發送者發送的,也可能是channel關閉了。

_, ok := <-ch與select配合使用的,當ok為false時,代表了channel已經close。下面解釋原因,_,ok := <-ch對應的函數是func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool),入參block含義是當前goroutine是否可阻塞,當block為false代表的是select操作,不可阻塞當前goroutine的在channel操作,否則是普通操作(即_, ok不在select中)。返回值selected代表當前操作是否成功,主要為select服務,返回received代表是否從channel讀到有效值。它有3種返回值情況:

block為false,即執行select時,如果channel為空,返回(false,false),代表select操作失敗,沒接收到值。否則,如果channel已經關閉,並且沒有數據,ep即接收數據的變量設置為零值,返回(true,false),代表select操作成功,但channel已關閉,沒讀到有效值。否則,其他讀到有效數據的情況,返回(true,ture)。

我們考慮_, ok := <-ch和select結合使用的情況。

情況1:當chanrecv返回(false,false)時,本質是select操作失敗了,所以相關的case會阻塞,不會執行,比如下面的代碼:

func main { ch := make(chan int) select { case v, ok := <-ch: fmt.Printf("v: %v, ok: %v\n", v, ok) default: fmt.Println("nothing") }}// 結果:// nothing

情況2:下面的結果會是零值和false:

func main { ch := make(chan int) // 增加關閉 close(ch) select { case v, ok := <-ch: fmt.Printf("v: %v, ok: %v\n", v, ok) }}// v: 0, ok: false

情況3的received為true,即_, ok中的ok為true,不做討論了,只討論ok為false的情況。

最後ok為false的時候,只有情況2,此時channel必然已經關閉,我們便可以在select中用ok判斷channel是否已經關閉。

用法

下面例子展示了,向channel寫數據然後關閉,依然可以從已關閉channel讀到有效數據,但channel關閉且沒有數據時,讀不到有效數據,ok為false,可以確定當前channel已關閉。

// demo_select6.gofunc main { ch := make(chan int, 1) // 發送1個數據關閉channel ch <- 1 close(ch) print("close channel\n") // 不停讀數據直到channel沒有有效數據 for { select { case v, ok := <-ch: print("v: ", v, ", ok:", ok, "\n") if !ok { print("channel is close\n") return } default: print("nothing\n") } }}// 結果// close channel// v: 1, ok:true// v: 0, ok:false// channel is close

更多見golang_step_by_step/channel/ok倉庫中ok和select的示例,或者閱讀channel源碼。

3. 使用select處理多個channel

場景

需要對多個通道進行同時處理,但只處理最先發生的channel時

原理

select可以同時監控多個通道的情況,只處理未阻塞的case。當通道為nil時,對應的case永遠為阻塞,無論讀寫。特殊關注:普通情況下,對nil的通道寫操作是要panic的

用法

// 分配job時,如果收到關閉的通知則退出,不分配jobfunc (h *Handler) handle(job *Job) { select { case h.jobCh<-job: return case <-h.stopCh: return }}

4. 使用channel的聲明控制讀寫權限

場景

協程對某個通道只讀或只寫時

目的:

使代碼更易讀、更易維護,防止只讀協程對通道進行寫數據,但通道已關閉,造成panic。

用法

如果協程對某個channel只有寫操作,則這個channel聲明為只寫。如果協程對某個channel只有讀操作,則這個channe聲明為只讀。

// 只有generator進行對outCh進行寫操作,返回聲明// <-chan int,可以防止其他協程亂用此通道,造成隱藏bugfunc 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) }}

5. 使用緩衝channel增強並發

場景

異步

原理

有緩衝通道可供多個協程同時處理,在一定程度可提高並發性。

用法

// 無緩衝ch1 := make(chan int)ch2 := make(chan int, 0)// 有緩衝ch3 := make(chan int, 1)

// 使用5個`do`協程同時處理輸入數據func test { inCh := generator(100) outCh := make(chan int, 10) for i := 0; i < 5; i { go do(inCh, outCh) } for r := range outCh { fmt.Println(r) }}func do(inCh <-chan int, outCh chan<- int) { for v := range inCh { outCh <- v * v }}

6. 為操作加上超時

場景

需要超時控制的操作

原理

使用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}

7. 使用time實現channel無阻塞讀寫

場景

並不希望在channel的讀寫上浪費時間

原理

是為操作加上超時的擴展,這裡的操作是channel的讀或寫

用法

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阻塞時,立即返回的效果

8. 使用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 } }}

9. 使用chan struct{}作為信號channel

場景

使用channel傳遞信號,而不是傳遞數據時

原理

沒數據需要傳遞時,傳遞空struct

用法

// 上例中的Handler.stopCh就是一個例子,stopCh並不需要傳遞任何數據// 只是要給所有協程發送退出的信號type Handler struct { stopCh chan struct{} reqCh chan *Request}

10. 使用channel傳遞結構體的指針而非結構體

場景

使用channel傳遞結構體數據時

原理

channel本質上傳遞的是數據的拷貝,拷貝的數據越小傳輸效率越高,傳遞結構體指針,比傳遞結構體更高效

用法

reqCh chan *Request// 好過reqCh chan Request

11. 使用channel傳遞channel

場景

使用場景有點多,通常是用來獲取結果。

原理

channel可以用來傳遞變量,channel自身也是變量,可以傳遞自己。

用法

下面示例展示了有序展示請求的結果,另一個示例可以見另外文章的版本3。

package mainimport ( "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}

推薦閱讀

本文介紹的channel特性,大多在過去的文章中已詳細介紹,可按需求閱讀。

Golang 並發模型系列:3. 並發協程的優雅退出Golang並發模型系列:4. 輕鬆入門 selectGolang並發模型系列:5. select進階Golang並發模型系列:6. 輕鬆入門協程池Go並發,雖然官方推薦 channel,但到底是channel還是鎖?
如果這篇文章對你有幫助,不妨關注下我的Github,有文章會收到通知。本文作者:大彬如果喜歡本文,隨意轉載,但請保留此原文連結:http://lessisbetter.site/2019/01/20/golang-channel-all-usage/

,
同类文章
葬禮的夢想

葬禮的夢想

夢見葬禮,我得到了這個夢想,五個要素的五個要素,水火只好,主要名字在外面,職業生涯良好,一切都應該對待他人治療誠意,由於小,吉利的冬天夢想,秋天的夢是不吉利的
找到手機是什麼意思?

找到手機是什麼意思?

找到手機是什麼意思?五次選舉的五個要素是兩名士兵的跡象。與他溝通很好。這是非常財富,它擅長運作,職業是仙人的標誌。單身男人有這個夢想,主要生活可以有人幫忙
我不怎麼想?

我不怎麼想?

我做了什麼意味著看到米飯烹飪?我得到了這個夢想,五線的主要土壤,但是Tu Ke水是錢的跡象,職業生涯更加真誠。他真誠地誠實。這是豐富的,這是夏瑞的巨星
夢想你的意思是什麼?

夢想你的意思是什麼?

你是什​​麼意思夢想的夢想?夢想,主要木材的五個要素,水的跡象,主營業務,主營業務,案子應該抓住魅力,不能疏忽,春天夢想的吉利夢想夏天的夢想不幸。詢問學者夢想
拯救夢想

拯救夢想

拯救夢想什麼意思?你夢想著拯救人嗎?拯救人們的夢想有一個現實,也有夢想的主觀想像力,請參閱週宮官方網站拯救人民夢想的詳細解釋。夢想著敵人被拯救出來
2022愛方向和生日是在[質量個性]中

2022愛方向和生日是在[質量個性]中

[救生員]有人說,在出生88天之前,胎兒已經知道哪天的出生,如何有優質的個性,將走在什麼樣的愛情之旅,將與生活生活有什么生活。今天
夢想切割剪裁

夢想切割剪裁

夢想切割剪裁什麼意思?你夢想切你的手是好的嗎?夢想切割手工切割手有一個真正的影響和反應,也有夢想的主觀想像力。請參閱官方網站夢想的細節,以削減手
夢想著親人死了

夢想著親人死了

夢想著親人死了什麼意思?你夢想夢想你的親人死嗎?夢想有一個現實的影響和反應,還有夢想的主觀想像力,請參閱夢想世界夢想死亡的親屬的詳細解釋
夢想搶劫

夢想搶劫

夢想搶劫什麼意思?你夢想搶劫嗎?夢想著搶劫有一個現實的影響和反應,也有夢想的主觀想像力,請參閱週恭吉夢官方網站的詳細解釋。夢想搶劫
夢想缺乏缺乏紊亂

夢想缺乏缺乏紊亂

夢想缺乏缺乏紊亂什麼意思?你夢想缺乏異常藥物嗎?夢想缺乏現實世界的影響和現實,還有夢想的主觀想像,請看官方網站的夢想組織缺乏異常藥物。我覺得有些東西缺失了