Golang中這些channel用法你了解嗎
channel 是什么
channel 是GO語言中一種特殊的類型,是連接并發(fā)goroutine
的管道
channel 通道是可以讓一個 goroutine 協(xié)程發(fā)送特定值到另一個 goroutine 協(xié)程的通信機制。
關于 channel 的原理,channel通道需要注意的地方,之前有分享過,可以查看如下文章
本次,我們主要分享的是關于 nil channel 通道,有緩沖通道,無緩沖通道 的常用方法以及巧妙使用的方式
巧用 nil 的 channel 通道
平日里使用的 channel 通道都是使用無緩沖,或者有緩沖的 channel 通道,或許使用為 nil 的 channel 通道還是比較少,甚至都不知道如何去使用 nil 的 channel 通道
我們先來看這么一個例子
- 創(chuàng)建兩個 channel c1, c2 ,數(shù)據類型是 struct{} ,用于占位
- 分別開辟兩個子協(xié)程,其中子協(xié)程 1 在 2 秒之后寫入數(shù)據給到 c1,另外一個子協(xié)程 2 在 1 秒之后寫入數(shù)據給到 c2
- 主協(xié)程循環(huán)等待阻塞讀取 c1 , c2 里面的數(shù)據,讀取后將對應的標識 ok1 / ok2 置為 true
- 當 ok1 和 ok2 都為 true 的時候,退出循環(huán),結束程序
func main() { c1, c2 := make(chan struct{}), make(chan struct{}) go func() { time.Sleep(time.Second * 2) c1 <- struct{}{} //close(c1) }() go func() { time.Sleep(time.Second * 1) c2 <- struct{}{} // close(c2) }() var ( ok1 bool ok2 bool ) for { select { case <-c1: ok1 = true fmt.Println("1") case <-c2: ok2 = true fmt.Println("2") } if ok1 && ok2 { break } } fmt.Println("program termination ... ") }
運行結果如下:
2
1
program termination ...
看上去效果一切正常,若此時,我們將上述代碼中的 close(c1)
和 close(c2)
的注釋去掉,我們再查看一下結果就回是這樣的:
...
2
2
2
1
program termination ...
出現(xiàn)這樣的問題是什么呢?是因為我們 close channel 通道之后,若還對這個通道寫入數(shù)據會 panic,若還從這個通道讀取數(shù)據會立即返回該通道類型的零值,而不會阻塞等待數(shù)據
因此才會有上述情況,那么這個時候,我就可以很好的用好這個 nil 的 channel,咱就可以這樣來調整一下關于通道使用的情況
修改為,從通道中讀取數(shù)據時,先判斷通道是否已經關閉,若關閉則將通道設置為 nil,若未關閉,則打印我們從通道中讀取的數(shù)據(此處模擬直接打印一個固定的值)
for { select { case _, ok := <-c1: if !ok { c1 = nil }else{ fmt.Println("1") } case _, ok := <-c2: if !ok { c2 = nil }else{ fmt.Println("2") } } if c1 == nil && c2 == nil { break } }
這種時候,我們就知道對于從通道中讀取數(shù)據,先去判斷通道是否關閉,若通道關閉了,那么我們直接顯示的給通道設置為 nil
這里是否會有這么一個疑問?關閉通道,通道變量不應該就變成 nil 了嗎?為什么我們還要自己去設置為 nil?
實際上這就是我們對于通道的基礎知識不扎實了,關閉通道后,通道本身并不會變?yōu)?nil。通道變量仍然持有通道的地址,只是通道的狀態(tài)變?yōu)榱艘殃P閉
巧用無緩沖 channel 通道
對于無緩沖的 channel 通道,只有在對其進行接收操作的 goroutine 協(xié)程和對其進行發(fā)送操作的 goroutine 協(xié)程都存在的情況下,通信才能進行,否則單方面的操作會讓對應的 goroutine 協(xié)程陷入阻塞狀態(tài),因為該 channel 通道沒有緩沖
使用無緩沖的 channel 通道,我們可以用在如下幾個方面
1.信號傳遞
信號傳遞我們就可以用在兩個協(xié)程一對一的傳遞信號上面,當然我們也可以使用在主協(xié)程主動通知所有子協(xié)程關閉的全場景下,這就是一對多的傳遞信號,相關的 demo 可以在這期文章中有展示
一對一(一個發(fā)一個收)
一對多(一個發(fā)多個收,此處可以是 協(xié)程 1 close 掉 通道,那么 多個協(xié)程默認都能夠讀取到通道的值是零值,此時多個子協(xié)程就可以根據通道的關閉狀態(tài)來處理后續(xù)的邏輯)
2.控制同步
GO語言倡導我們不要通過共享內存來通信,而應該通過通信來共享內存,此處 channel 就是這樣設計的,當然如果需要有更高的性能,那么我們還是可以使用更加低級的GO語言原語 sync 包中的鎖機制
可以點擊查看往期文章:sync 鎖機制
巧用有緩沖 channel 通道
1.用作隊列
用作隊列應該是比較好理解的,隊列先入先出 FIFO,給 channel 通道設置明確的緩沖區(qū),例如 ch:=make(chan int, 10)
多個協(xié)程就可以異步的并發(fā)處理該隊列,由于有緩沖的 channel 通道中有一定的容量,因此,對于協(xié)程讀取通道中數(shù)據時,存在阻塞的情況相對無緩沖的通道來說就會少很多,相應的在一定程度上就提升了性能
對于有緩沖的 channel 通道,channel 通道滿的時候,寫入數(shù)據會阻塞,讀取數(shù)據正常處理, channel 通道空的時候,寫入數(shù)據正常,讀取數(shù)據會阻塞
2.用作信號量
有緩沖的 channel 通道還可以用來計數(shù),例如我們有 15 個 job,可是目前只有 3 個 worker,那么同一時間,只會有 3 個worker 來干活,我們就可以使用通道來查看目前有多少個 worker 在工作,寫一個簡單的 demo
- 創(chuàng)建 j 和 worker channel 通道,
- 子協(xié)程 1 寫 15 個任務給到 j 通道中,寫完 15 個任務到 j 中便關閉自己的通道(因為后續(xù)我們需要使用 for...range 的方式讀取通道)
- 使用 sync.WaitGroup 管控開辟的 3 個協(xié)程,模擬 3 個 工人去干活
- 能夠從寫入數(shù)據到 worker channel 通道中,則開始干活,干完之后,從 worker channel 通道中讀出數(shù)據
func main() { j := make(chan int, 15) worker := make(chan int, 3) go func() { for i := 0; i < 15; i++ { j <- i } close(j) }() var wg sync.WaitGroup for job := range j { wg.Add(1) go func(job int) { defer wg.Done() worker <- job // 模擬干活 fmt.Println("正在執(zhí)行 job : ", job) time.Sleep(time.Second * 1) <-worker }(job) } wg.Wait() fmt.Println("program termination ... ") }
感興趣的 xdm 的可以復制代碼運行一下,可以看到效果是 3 個 job 一起打印,間隔 1 秒后,又是 3 個 job 一起打印的
select 和 channel 通道如何結合使用?
1.心跳
func main() { h := time.NewTicker(2 * time.Second) defer h.Stop() for { select { case <-h.C: // 模擬處理心跳 fmt.Println("hhh") } } }
2.使用 default
select ...default
這個組合就不必過多贅述了,就是在我們阻塞讀取通道數(shù)據時,若當前時間沒有從任何一個通道中讀取到數(shù)據,則默認走 default 里面的邏輯
3.超時機制
超時機制使用的也是非常頻繁的,很多時候為了方便,可能我們會使用例如<- time.After(10 * time.Second)
的方式,使用這種方式,GO 語言會維護一個最小堆,當時間到了,通道被喚醒的時候,就會從最小堆頂取出 timer 對象,再執(zhí)行 timer 中的函數(shù),執(zhí)行完畢之后,自行就會做刪除,自行就會做 GC
可是在上述這種方式使用比較多的時候,會給程序帶來 GC 的壓力,我們完全可以入如下方式來實現(xiàn)超時機制,顯示的去做 GC
func main() { c := time.NewTimer(10 * time.Second) defer c.Stop() for { select { case <-c.C: fmt.Println("program overtime ") return } } }
總結
本次演示了關于 nil channel,有緩沖 channel ,無緩沖 channel , select 如何與 channel 配合使用,上述 demo 完全可以復制下來,xdm 可以自行運行,查看效果
以上就是Golang中這些channel用法你了解嗎的詳細內容,更多關于Go channel的資料請關注腳本之家其它相關文章!
相關文章
go?zero微服務實戰(zhàn)性能優(yōu)化極致秒殺
這篇文章主要為大家介紹了go-zero微服務實戰(zhàn)性能優(yōu)化極致秒殺功能實現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-07-07go語言實現(xiàn)字符串與其它類型轉換(strconv包)
strconv包是Go語言標準庫的一部分,主要提供字符串與基本數(shù)據類型之間的轉換功能,使用strconv包可以方便地在不同類型之間進行轉換,滿足日常編程中的需求,感興趣的可以了解一下2024-10-10