Golang中channel的用法舉例詳解
前言
在golang并發(fā)編程實(shí)踐中,channel的正確運(yùn)用直接影響程序的健壯性和執(zhí)行效率。本文將深入探討幾種提升channel使用效能的典型場(chǎng)景與實(shí)現(xiàn)策略。
1.channel的類別
主要有2種:
有緩存 Channel(buffered channel),使用 make(chan type, capacity int) 創(chuàng)建
無(wú)緩存 Channel(unbuffered channel),使用 make(chan type) 創(chuàng)建
其中 ,type為 Channel 傳遞數(shù)據(jù)的類型,capacity 為緩存大小。
unbuffered channel:阻塞、同步模式
- sender端向channel中send一個(gè)數(shù)據(jù),然后阻塞,直到receiver端將此數(shù)據(jù)receive
- receiver端一直阻塞,直到sender端向channel發(fā)送了一個(gè)數(shù)據(jù)
buffered channel:非阻塞、異步模式
- sender端可以向channel中send多個(gè)數(shù)據(jù)(只要channel容量未滿),容量滿之前不會(huì)阻塞
- receiver端按照隊(duì)列的方式(FIFO,先進(jìn)先出)從buffered channel中按序receive其中數(shù)據(jù)
2.channel的狀態(tài)
主要有3種狀態(tài):
- actived,正常狀態(tài),可以正常的讀receive,寫send
- nil,未初始化狀態(tài),即只進(jìn)行了聲明但還尚未分配內(nèi)存,或者是channel被主動(dòng)賦值為了nil
- closed,關(guān)閉狀態(tài)。
注意:channel被close后,它的狀態(tài)并不是nil。
因?yàn)閚il channel是不能讀取的,會(huì)panic。但是close后的channel,如果管道里還有數(shù)據(jù),是可以通過(guò)range正常讀取出來(lái)的。
3.channel的操作
常用的操作有這4種:
- 讀,<- ch
- 寫,ch <-
- 關(guān)閉, close(ch)
- 遍歷,for v := range ch {}
4.組合操作
前面所說(shuō)的3種狀態(tài),和4種操作,組合起來(lái)后的結(jié)果如下:
操作\狀態(tài) | actived | close | nil |
---|---|---|---|
<-ch (讀) | 成功或者阻塞 | 零值 | 死鎖 |
ch<- (寫) | 成功或者阻塞 | panic | 死鎖 |
close | 成功 | panic(重復(fù)關(guān)閉) | panic |
for range | 成功 | 成功(break) | 死鎖 |
有2個(gè)特殊點(diǎn)需要說(shuō)明:
4.1 for range closed_chan
場(chǎng)景1:channel已關(guān)閉且無(wú)剩余數(shù)據(jù)循環(huán)立即退出:如果channel在關(guān)閉時(shí)已經(jīng)沒(méi)有數(shù)據(jù),for range循環(huán)不會(huì)執(zhí)行任何迭代,直接終止。
ch := make(chan int) close(ch) for v := range ch { // 循環(huán)不執(zhí)行,直接退出 // 代碼不會(huì)執(zhí)行 }
場(chǎng)景2:channel已關(guān)閉但有剩余數(shù)據(jù)讀取所有數(shù)據(jù)后退出:如果channel關(guān)閉時(shí)仍有數(shù)據(jù)未被讀取,for range會(huì)讀取所有剩余數(shù)據(jù),然后正常退出循環(huán)。
ch := make(chan int, 2) ch <- 1 ch <- 2 close(ch) for v := range ch { fmt.Println(v) // 輸出1、2 }
場(chǎng)景3:未關(guān)閉的Channel會(huì)導(dǎo)致阻塞如果channel未關(guān)閉且無(wú)數(shù)據(jù),for range會(huì)一直阻塞等待數(shù)據(jù),可能導(dǎo)致goroutine泄漏。
4.2 nil channel在select中的行為
如上面的表格所展示:對(duì)nil channel進(jìn)行讀寫,都會(huì)導(dǎo)致死鎖(永久阻塞)。
無(wú)論是發(fā)送(ch <- v)還是接收(<- ch),操作nil channel的代碼會(huì)永久阻塞。
var ch chan int // ch是nil ch <- 1 // 永久阻塞(發(fā)送到nil channel) <-ch // 永久阻塞(從nil channel接收)
但是,如果是在select中,則select會(huì)忽略nil channel的case:
- 當(dāng)select的某個(gè)case操作的是nil channel時(shí),該case會(huì)被視為未就緒,直接跳過(guò)。
- 如果其他case中有就緒的channel操作,select會(huì)正常執(zhí)行這些case。
- 如果所有case都未就緒(包括nil channel的case),且沒(méi)有default分支,則select會(huì)阻塞等待,但不會(huì)觸發(fā)死鎖(除非整個(gè)goroutine再無(wú)其他代碼可執(zhí)行)。
場(chǎng)景1:nil channel與其他有效case共存
var ch chan int // ch是nil timeout := time.After(1 * time.Second) select { case <-ch: // 該case被跳過(guò)(nil channel) fmt.Println("Received from ch") case <-timeout: fmt.Println("Timeout") // 1秒后執(zhí)行 }
結(jié)果:select會(huì)忽略<-ch(nil channel),等待timeout就緒后執(zhí)行。
不會(huì)死鎖:因?yàn)榇嬖谄渌行ase(timeout)。
場(chǎng)景2:所有case均為nil channel
var ch1, ch2 chan int // 均為nil select { case <-ch1: // 被跳過(guò) case <-ch2: // 被跳過(guò) }
結(jié)果:select會(huì)永久阻塞,但如果整個(gè)goroutine沒(méi)有其他代碼可執(zhí)行,會(huì)觸發(fā)死鎖(fatal error: all goroutines are asleep - deadlock!)。
關(guān)鍵點(diǎn):死鎖是否發(fā)生取決于整個(gè)goroutine的狀態(tài),而不僅僅是select本身。
select的設(shè)計(jì)機(jī)制:
- select會(huì)動(dòng)態(tài)檢查所有case的就緒狀態(tài),跳過(guò)未就緒的case(包括nil channel)。
- 只要存在其他就緒的case(如定時(shí)器、非nil channel等),select就能正常執(zhí)行。
5.channel常見(jiàn)用法
5.1 使用for range讀取channel
場(chǎng)景:需要持續(xù)不斷從channel讀取數(shù)據(jù)
說(shuō)明:采用range關(guān)鍵字進(jìn)行通道遍歷,當(dāng)發(fā)送端關(guān)閉通道時(shí),循環(huán)自動(dòng)終止。這種方式避免了手動(dòng)檢測(cè)通道狀態(tài)的繁瑣操作,同時(shí)保證不會(huì)讀取到無(wú)效零值。
示例:
for v := range ch{ doSomething(v) }
5.2 使用_, ok判斷channel狀態(tài)
場(chǎng)景:雙返回值驗(yàn)證機(jī)制,用于讀取channel但不確定channel是否已經(jīng)關(guān)閉
說(shuō)明:當(dāng)不確定通道是否關(guān)閉時(shí),采用特殊語(yǔ)法進(jìn)行安全校驗(yàn)。返回值狀態(tài)指示符ok為true表示成功接收有效數(shù)據(jù),false則標(biāo)志通道已關(guān)閉。
示例:
if v, ok := <-ch; ok { doSomething(v) }
5.3 使用select進(jìn)行多路channel處理
場(chǎng)景:選擇性路由機(jī)制。select對(duì)多個(gè)channel同時(shí)處理時(shí),會(huì)先處理最先發(fā)生的channel
說(shuō)明:當(dāng)需要同時(shí)監(jiān)聽(tīng)多個(gè)數(shù)據(jù)源時(shí),select語(yǔ)句可實(shí)現(xiàn)智能路由。注意nil channel的特殊處理,讀取會(huì)永久阻塞,寫入將導(dǎo)致運(yùn)行時(shí)異常,但是注意4.2節(jié)中提到的select對(duì)于nil channel的特殊情況。
示例:
select { case taskCh <- newTask: processTask() case <-shutdownSignal: terminate() }
5.4 使用channel控制讀寫權(quán)限
場(chǎng)景:協(xié)程對(duì)某個(gè)channel只讀或者只寫時(shí)
說(shuō)明:通過(guò)聲明只讀/只寫類型channel,增強(qiáng)代碼可維護(hù)性。這種類型約束可防止意外的反向操作,降低運(yùn)行時(shí)panic風(fēng)險(xiǎn)。
示例:
// 只寫操作 func writeOnly(n int) <-chan int { ch := make(chan int) go func() { defer close(ch) //必須關(guān)閉channel以通知外面的其它工作協(xié)程退出. //不然,在外面無(wú)法close一個(gè)單向的channel,導(dǎo)致死鎖 for i:=0; i<n; i++ { ch <- i } }() return out }
// 只讀操作 func readOnly(in <-chan int) { for v := range in { doSomething(v) } }
5.5 使用channel進(jìn)行并發(fā)控制
場(chǎng)景:同步,異步和并發(fā)調(diào)用
說(shuō)明:有緩存chan(buffered channel)是異步的,可提供給多個(gè)協(xié)程同時(shí)處理,提高系統(tǒng)的并發(fā)性能。而無(wú)緩存chan(unbuffered channel)是同步的。
// 有緩存channel ch1 := make(chan int, 1) // 無(wú)緩存channel ch2 := make(chan int) ch3 := make(chan int, 0)
示例: 并發(fā)處理模型
func doWorker(inCh <-chan int, outCh chan<- int, wg *sync.WaitGroup) { defer wg.Done() for v := range inCh { outCh <- v * 10 } } func concurrentProcess() { inCh := writeOnly(100) outCh := make(chan int, 10) var wg sync.WaitGroup // 同時(shí)運(yùn)行5個(gè)協(xié)程,從inCh中并發(fā)讀取數(shù)據(jù),并發(fā)寫入outCh for i := 0; i < 5; i++ { wg.Add(1) go doWorker(inCh, outCh, &wg) } //等待所有worker完成并關(guān)閉outCh go func() { wg.Wait() close(outCh) }() for v := range outCh { fmt.Println(v) } }
5.6 超時(shí)控制
場(chǎng)景:在一些需要進(jìn)行超時(shí)控制的情況下
說(shuō)明:通過(guò)結(jié)合定時(shí)器(select & time.After)實(shí)現(xiàn)操作時(shí)效控制,避免長(zhǎng)期阻塞。特別注意定時(shí)channel的資源釋放問(wèn)題。
示例:
func doWorker() <-chan int { ch := make(chan int) go func() { // do something for ch }() return ch } func doWithTimeout(timeout time.Duration) (int, error) { select { case v := <-doWorker2(): return v, nil case <-time.After(timeout): return 0, fmt.Errorf("timeout") } } func main() { v, err := doWithTimeout(1 * time.Second) fmt.Printf("v:%d, err:%v\n", v, err) }
輸出: v:0, err:timeout
5.7 非阻塞讀寫channel
場(chǎng)景:對(duì)channel進(jìn)行非阻塞式的讀或者寫
說(shuō)明:通過(guò)default分支實(shí)現(xiàn)即時(shí)返回,適用于不可阻塞的實(shí)時(shí)系統(tǒng)。需要與帶緩沖通道配合使用。
示例:
非阻塞的立即返回方案
func unblockWrite(ch chan int, v int) error { select { case ch <- v: return nil default: return fmt.Errorf("channel write blocked") } } func unblockRead(ch chan int) (int, error) { select { case v := <-ch: return v, nil default: return 0, fmt.Errorf("channel read blocked") } }
5.8 級(jí)聯(lián)close channel
場(chǎng)景:進(jìn)行優(yōu)雅關(guān)閉,廣播通知所有協(xié)程退出
說(shuō)明:關(guān)閉channel會(huì)產(chǎn)生廣播效應(yīng),所有接收此channel的協(xié)程都會(huì)收到零值。結(jié)合sync.WaitGroup可實(shí)現(xiàn)安全終止。
示例:
type Manager struct { stopCh chan struct{} workCh chan struct{} wg sync.WaitGroup } func (m *Manager) Shutdown() { close(m.stopCh) //等待所有其它協(xié)程退出 m.wg.Wait() } func (m *Manager) workLoop() { for { select { case v := <-m.workCh: go doWorker(v) case <-m.stopCh: //close后會(huì)讀取到零值 return } } }
5.9 信號(hào)事件載體
場(chǎng)景:定義的channel,僅用來(lái)傳遞事件/信號(hào),無(wú)需傳遞數(shù)據(jù)
說(shuō)明:當(dāng)僅需事件通知而不傳遞數(shù)據(jù)時(shí),采用空結(jié)構(gòu)體通道可最小化內(nèi)存消耗。
示例:在5.8節(jié)中,stopCh就是用于事件傳遞的channel。它并不需要傳遞數(shù)據(jù),只需要向其它的所有協(xié)程發(fā)出終止信號(hào)。
5.10高效數(shù)據(jù)傳輸
場(chǎng)景:用于性能優(yōu)化,傳遞指針,而非拷貝數(shù)據(jù)
說(shuō)明:對(duì)于大型數(shù)據(jù)結(jié)構(gòu),傳遞指針可顯著降低通道操作的復(fù)制開(kāi)銷。需注意并發(fā)訪問(wèn)時(shí)的數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題。
示例:
type Payload struct { // 大數(shù)據(jù)結(jié)構(gòu) } payloadChan := make(chan *Payload, 10)
以上就是關(guān)于channel的一些實(shí)踐技巧,合理運(yùn)用能有效提升并發(fā)程序的執(zhí)行效率和代碼可維護(hù)性。開(kāi)發(fā)者應(yīng)根據(jù)具體場(chǎng)景選擇適當(dāng)?shù)哪J?,并注意資源管理與并發(fā)安全問(wèn)題。
總結(jié)
到此這篇關(guān)于Golang中channel用法舉例詳解的文章就介紹到這了,更多相關(guān)Golang中channel用法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
用Go語(yǔ)言編寫一個(gè)簡(jiǎn)單的分布式系統(tǒng)
這篇文章主要介紹了用Go語(yǔ)言編寫一個(gè)簡(jiǎn)單的分布式系統(tǒng),文中的代碼示例講解的非常詳細(xì),對(duì)我們的學(xué)習(xí)或工作有一定的幫助,感興趣的小伙伴跟著小編一起來(lái)看看吧2023-08-08golang 中signal包的Notify用法說(shuō)明
這篇文章主要介紹了golang 中signal包的Notify用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-03-03Go語(yǔ)言k8s?kubernetes使用leader?election實(shí)現(xiàn)選舉
這篇文章主要為大家介紹了Go語(yǔ)言?k8s?kubernetes?使用leader?election選舉,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10Golang利用channel協(xié)調(diào)協(xié)程的方法詳解
go?當(dāng)中的并發(fā)編程是通過(guò)goroutine來(lái)實(shí)現(xiàn)的,利用channel(管道)可以在協(xié)程之間傳遞數(shù)據(jù),所以本文就來(lái)講講Golang如何利用channel協(xié)調(diào)協(xié)程吧2023-05-05適合PHP同學(xué)的GoFrame框架使用體驗(yàn)及學(xué)習(xí)建議
這篇文章主要為大家介紹了非常適合PHP同學(xué)使用的GoFrame框架設(shè)計(jì)思想使用體驗(yàn)及學(xué)習(xí)建議介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06golang中按照結(jié)構(gòu)體的某個(gè)字段排序?qū)嵗a
在任何編程語(yǔ)言中,關(guān)乎到數(shù)據(jù)的排序都會(huì)有對(duì)應(yīng)的策略,下面這篇文章主要給大家介紹了關(guān)于golang中按照結(jié)構(gòu)體的某個(gè)字段排序的相關(guān)資料,需要的朋友可以參考下2022-05-05golang替換無(wú)法顯示的特殊字符(\u0000,?\000,?^@)
這篇文章主要介紹了golang替換無(wú)法顯示的特殊字符,包括的字符有\(zhòng)u0000,?\000,?^@等,下文詳細(xì)資料,需要的小伙伴可以參考一下2022-04-04Golang設(shè)計(jì)模式中的橋接模式詳細(xì)講解
橋接模式是一種結(jié)構(gòu)型設(shè)計(jì)模式,通過(guò)橋接模式可以將抽象部分和它的實(shí)現(xiàn)部分分離,本文主要介紹了GoLang橋接模式,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2023-01-01