Golang通道的無阻塞讀寫的方法示例
無論是無緩沖通道,還是有緩沖通道,都存在阻塞的情況,但其實(shí)有些情況,我們并不想讀數(shù)據(jù)或者寫數(shù)據(jù)阻塞在那里,有1個(gè)唯一的解決辦法,那就是使用select結(jié)構(gòu)。
這篇文章會(huì)介紹,哪些情況會(huì)存在阻塞,以及如何使用select解決阻塞。
阻塞場(chǎng)景
阻塞場(chǎng)景共4個(gè),有緩存和無緩沖各2個(gè)。
無緩沖通道的特點(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ò)誤。
有緩存通道的特點(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!
}
使用Select實(shí)現(xiàn)無阻塞讀寫
select是執(zhí)行選擇操作的一個(gè)結(jié)構(gòu),它里面有一組case語句,它會(huì)執(zhí)行其中無阻塞的那一個(gè),如果都阻塞了,那就等待其中一個(gè)不阻塞,進(jìn)而繼續(xù)執(zhí)行,它有一個(gè)default語句,該語句是永遠(yuǎn)不會(huì)阻塞的,我們可以借助它實(shí)現(xiàn)無阻塞的操作。
下面示例代碼是使用select修改后的無緩沖通道和有緩沖通道的讀寫,以下函數(shù)可以直接通過main函數(shù)調(diào)用,其中的Ouput的注釋是運(yùn)行結(jié)果,從結(jié)果能看出,在通道不可讀或者不可寫的時(shí)候,不再阻塞等待,而是直接返回。
// 無緩沖通道讀
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
}
// 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 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
}
// 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")
}
}
使用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,如果依然無法讀寫,就即刻返回,修改一下會(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
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Go開發(fā)go-optioner工具實(shí)現(xiàn)輕松生成函數(shù)選項(xiàng)模式代碼
go-optioner?是一個(gè)在?Go?代碼中生成函數(shù)選項(xiàng)模式代碼的工具,可以根據(jù)給定的結(jié)構(gòu)定義自動(dòng)生成相應(yīng)的選項(xiàng)代碼,下面就來聊聊go-optioner是如何使用的吧2023-07-07
go語言實(shí)現(xiàn)mqtt協(xié)議的實(shí)踐
MQTT是一個(gè)基于客戶端-服務(wù)器的消息發(fā)布/訂閱傳輸協(xié)議。本文主要介紹了go語言實(shí)現(xiàn)mqtt協(xié)議的實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
golang自帶的死鎖檢測(cè)并非銀彈的問題小結(jié)
Go語言自帶的死鎖檢測(cè)機(jī)制并不萬能,它只對(duì)用戶創(chuàng)建的協(xié)程進(jìn)行檢測(cè),并且在特定條件下可能會(huì)“失靈”,死鎖檢測(cè)的觸發(fā)時(shí)機(jī)和檢測(cè)內(nèi)容也有限制,因此不能完全避免死鎖問題,為了預(yù)防死鎖,應(yīng)該在編寫代碼時(shí)提前進(jìn)行設(shè)計(jì)和測(cè)試,感興趣的朋友跟隨小編一起看看吧2025-01-01
go運(yùn)算符對(duì)變量和值執(zhí)行操作示例詳解
這篇文章主要為大家介紹了go運(yùn)算符對(duì)變量和值執(zhí)行操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
基于go語言實(shí)現(xiàn)圖片驗(yàn)證碼的代碼示例
這篇文章主要為大家詳細(xì)介紹了基于go語言實(shí)現(xiàn)圖片驗(yàn)證碼的代碼示例,文中的示例代碼簡潔易懂,具有一定的借鑒價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-10-10

