Golang通道的無阻塞讀寫的方法示例
無論是無緩沖通道,還是有緩沖通道,都存在阻塞的情況,但其實有些情況,我們并不想讀數(shù)據(jù)或者寫數(shù)據(jù)阻塞在那里,有1個唯一的解決辦法,那就是使用select結(jié)構(gòu)。
這篇文章會介紹,哪些情況會存在阻塞,以及如何使用select解決阻塞。
阻塞場景
阻塞場景共4個,有緩存和無緩沖各2個。
無緩沖通道的特點是,發(fā)送的數(shù)據(jù)需要被讀取后,發(fā)送才會完成,它阻塞場景:
- 通道中無數(shù)據(jù),但執(zhí)行讀通道。
- 通道中無數(shù)據(jù),向通道寫數(shù)據(jù),但無協(xié)程讀取。
// 場景1 func ReadNoDataFromNoBufCh() { noBufCh := make(chan int) <-noBufCh fmt.Println("read from no buffer channel success") // Output: // fatal error: all goroutines are asleep - deadlock! } // 場景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é)果,每一個函數(shù)都由于阻塞在通道操作而無法繼續(xù)向下執(zhí)行,最后報了死鎖錯誤。
有緩存通道的特點是,有緩存時可以向通道中寫入數(shù)據(jù)后直接返回,緩存中有數(shù)據(jù)時可以從通道中讀到數(shù)據(jù)直接返回,這時有緩存通道是不會阻塞的,它阻塞的場景是:
- 通道的緩存無數(shù)據(jù),但執(zhí)行讀通道。
- 通道的緩存已經(jīng)占滿,向通道寫數(shù)據(jù),但無協(xié)程讀。
// 場景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! } // 場景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實現(xiàn)無阻塞讀寫
select是執(zhí)行選擇操作的一個結(jié)構(gòu),它里面有一組case語句,它會執(zhí)行其中無阻塞的那一個,如果都阻塞了,那就等待其中一個不阻塞,進而繼續(xù)執(zhí)行,它有一個default語句,該語句是永遠不會阻塞的,我們可以借助它實現(xiàn)無阻塞的操作。
下面示例代碼是使用select修改后的無緩沖通道和有緩沖通道的讀寫,以下函數(shù)可以直接通過main函數(shù)調(diào)用,其中的Ouput的注釋是運行結(jié)果,從結(jié)果能看出,在通道不可讀或者不可寫的時候,不再阻塞等待,而是直接返回。
// 無緩沖通道讀 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)實現(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)實現(xiàn)通道寫 func WriteChWithSelect(ch chan int) error { select { case ch <- 1: return nil default: return errors.New("channel blocked, can not write") } }
使用Select+超時改善無阻塞讀寫
使用default實現(xiàn)的無阻塞通道阻塞有一個缺陷:當通道不可讀或?qū)懙臅r候,會即可返回。實際場景,更多的需求是,我們希望,嘗試讀一會數(shù)據(jù),或者嘗試寫一會數(shù)據(jù),如果實在沒法讀寫,再返回,程序繼續(xù)做其它的事情。
使用定時器替代default可以解決這個問題。比如,我給通道讀寫數(shù)據(jù)的容忍時間是500ms,如果依然無法讀寫,就即刻返回,修改一下會是這樣:
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é)果就會變成超時返回:
read time out
write time out
read time out
write time out
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解用Go語言實現(xiàn)工廠模式(Golang經(jīng)典編程案例)
這篇文章主要介紹了詳解用Go語言實現(xiàn)工廠模式(Golang經(jīng)典編程案例),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04Go語言kube-scheduler深度剖析開發(fā)之scheduler初始化
這篇文章主要介紹了Go語言kube-scheduler深度剖析開發(fā)之scheduler初始化實現(xiàn)過程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04Go語言編譯程序從后臺運行,不出現(xiàn)dos窗口的操作
這篇文章主要介紹了Go語言編譯程序從后臺運行,不出現(xiàn)dos窗口的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04