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