Golang通道阻塞情況與通道無阻塞實(shí)現(xiàn)小結(jié)
一、通道阻塞原理
在Go語言中,通道會(huì)在以下情況下發(fā)生阻塞:
- 如果通道已滿,并且沒有協(xié)程在讀取通道中的數(shù)據(jù),那么任何試圖將數(shù)據(jù)寫入通道的協(xié)程都會(huì)被阻塞,直到有空間可用為止。
- 如果通道為空,并且沒有協(xié)程在等待從通道中讀取數(shù)據(jù),那么任何試圖從通道中讀取數(shù)據(jù)的協(xié)程都會(huì)被阻塞,直到有數(shù)據(jù)可用為止。
二、通道阻塞場(chǎng)景
在channel中,無論是有緩存通道、無緩沖通道都存在阻塞的情況。阻塞場(chǎng)景共4個(gè),有緩存和無緩沖各2個(gè)。
2.1 無緩沖通道
無緩沖通道的特點(diǎn)是,發(fā)送的數(shù)據(jù)需要被讀取后,發(fā)送才會(huì)完成,它阻塞場(chǎng)景:
- 通道中無數(shù)據(jù),但執(zhí)行讀通道。
- 通道中無數(shù)據(jù),向通道寫數(shù)據(jù),但無協(xié)程讀取。
// 場(chǎng)景1 func ReadNoDataFromNoBufCh() { noBufCh := make(chan int) <-noBufCh fmt.Println("read from no buffer channel success") // Output: // fatal error: all goroutines are asleep - deadlock! } // 場(chǎng)景2 func WriteNoBufCh() { ch := make(chan int) ch <- 1 fmt.Println("write success no block") // Output: // fatal error: all goroutines are asleep - deadlock! }
注:示例代碼中的Output注釋代表函數(shù)的執(zhí)行結(jié)果
每一個(gè)函數(shù)都由于阻塞在通道操作而無法繼續(xù)向下執(zhí)行,最后報(bào)了死鎖錯(cuò)誤。
2.2 有緩存通道
有緩存通道的特點(diǎn)是,有緩存時(shí)可以向通道中寫入數(shù)據(jù)后直接返回,緩存中有數(shù)據(jù)時(shí)可以從通道中讀到數(shù)據(jù)直接返回,這時(shí)有緩存通道是不會(huì)阻塞的,它阻塞場(chǎng)景是:
- 通道的緩存無數(shù)據(jù),但執(zhí)行讀通道。
- 通道的緩存已經(jīng)占滿,向通道寫數(shù)據(jù),但無協(xié)程讀。
// 場(chǎng)景1 func ReadNoDataFromBufCh() { bufCh := make(chan int, 1) <-bufCh fmt.Println("read from no buffer channel success") // Output: // fatal error: all goroutines are asleep - deadlock! } // 場(chǎng)景2 func WriteBufChButFull() { ch := make(chan int, 1) // make ch full ch <- 100 ch <- 1 fmt.Println("write success no block") // Output: // fatal error: all goroutines are asleep - deadlock! }
三、通道無阻塞讀寫
3.1 Select實(shí)現(xiàn)無阻塞讀寫
下面示例代碼是使用select修改后的無緩沖通道和有緩沖通道的讀寫,以下函數(shù)可以直接通過main函數(shù)調(diào)用;
// 1.select結(jié)構(gòu)實(shí)現(xiàn)通道讀 func ReadWithSelect(ch chan int) (x int, err error) { select { case x = <-ch: return x, nil default: return 0, errors.New("channel has no data") } } // 無緩沖通道讀 func ReadNoDataFromNoBufChWithSelect() { bufCh := make(chan int) if v, err := ReadWithSelect(bufCh); err != nil { fmt.Println(err) } else { fmt.Printf("read: %d\n", v) } // Output: // channel has no data } // 有緩沖通道讀 func ReadNoDataFromBufChWithSelect() { bufCh := make(chan int, 1) if v, err := ReadWithSelect(bufCh); err != nil { fmt.Println(err) } else { fmt.Printf("read: %d\n", v) } // Output: // channel has no data } // 2. select結(jié)構(gòu)實(shí)現(xiàn)通道寫 func WriteChWithSelect(ch chan int) error { select { case ch <- 1: return nil default: return errors.New("channel blocked, can not write") } } // 無緩沖通道寫 func WriteNoBufChWithSelect() { ch := make(chan int) if err := WriteChWithSelect(ch); err != nil { fmt.Println(err) } else { fmt.Println("write success") } // Output: // channel blocked, can not write } // 有緩沖通道寫 func WriteBufChButFullWithSelect() { ch := make(chan int, 1) // make ch full ch <- 100 if err := WriteChWithSelect(ch); err != nil { fmt.Println(err) } else { fmt.Println("write success") } // Output: // channel blocked, can not write }
注:示例代碼中的Output注釋代表函數(shù)的執(zhí)行結(jié)果
從結(jié)果能看出,在通道不可讀或者不可寫的時(shí)候,不再阻塞等待,而是直接返回。
3.2 使用Select+超時(shí)改善無阻塞讀寫
使用default實(shí)現(xiàn)的無阻塞通道阻塞有一個(gè)缺陷:當(dāng)通道不可讀或?qū)懙臅r(shí)候,會(huì)即可返回。實(shí)際場(chǎng)景,更多的需求是,我們希望嘗試讀一會(huì)數(shù)據(jù),或者嘗試寫一會(huì)數(shù)據(jù),如果實(shí)在沒法讀寫再返回,程序繼續(xù)做其它的事情。
使用定時(shí)器替代default可以解決這個(gè)問題,給通道增加讀寫數(shù)據(jù)的容忍時(shí)間,如果500ms內(nèi)無法讀寫,就即刻返回。示例代碼修改一下會(huì)是這樣:
func ReadWithSelect(ch chan int) (x int, err error) { timeout := time.NewTimer(time.Microsecond * 500) select { case x = <-ch: return x, nil case <-timeout.C: return 0, errors.New("read time out") } } func WriteChWithSelect(ch chan int) error { timeout := time.NewTimer(time.Microsecond * 500) select { case ch <- 1: return nil case <-timeout.C: return errors.New("write time out") } }
結(jié)果就會(huì)變成超時(shí)返回:
read time out
write time out
read time out
write time out
四、總結(jié)
本篇文章介紹了在Go語言中,通道會(huì)在以下情況下發(fā)生阻塞:
- 如果通道已滿,并且沒有協(xié)程在讀取通道中的數(shù)據(jù),那么任何試圖將數(shù)據(jù)寫入通道的協(xié)程都會(huì)被阻塞,直到有空間可用為止。
- 如果通道為空,并且沒有協(xié)程在等待從通道中讀取數(shù)據(jù),那么任何試圖從通道中讀取數(shù)據(jù)的協(xié)程都會(huì)被阻塞,直到有數(shù)據(jù)可用為止。
以及解決阻塞的2種辦法:
- 使用select的default語句,在channel不可讀寫時(shí),即可返回
- 使用select+定時(shí)器,在超時(shí)時(shí)間內(nèi),channel不可讀寫,則返回
五、參考鏈接
- http://lessisbetter.site/2018/11/03/Golang-channel-read-and-write-without-blocking/
- https://studygolang.com/articles/6024
到此這篇關(guān)于Golang通道阻塞情況與通道無阻塞實(shí)現(xiàn)小結(jié)的文章就介紹到這了,更多相關(guān)Golang通道阻塞情況與通道無阻塞內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言并發(fā)編程之互斥鎖Mutex和讀寫鎖RWMutex
Go 語言中提供了很多同步工具,本文將介紹互斥鎖Mutex和讀寫鎖RWMutex的使用方法,想要具體了解的小伙伴,請(qǐng)參考下面文章詳細(xì)內(nèi)容,希望對(duì)你有所幫助2021-10-10Go語言error的設(shè)計(jì)理念及背景演化詳解
這篇文章主要為大家介紹了Go語言error的設(shè)計(jì)理念及背景演化詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12golang的time包:秒、毫秒、納秒時(shí)間戳輸出方式
這篇文章主要介紹了golang的time包:秒、毫秒、納秒時(shí)間戳輸出方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12Goland使用Go Modules創(chuàng)建/管理項(xiàng)目的操作
這篇文章主要介紹了Goland使用Go Modules創(chuàng)建/管理項(xiàng)目的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-05-05在Golang中使用http.FileServer返回靜態(tài)文件的操作
這篇文章主要介紹了在Golang中使用http.FileServer返回靜態(tài)文件的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12