Golang併發:一招掌握無阻塞通道讀寫

介紹Golang併發的模型寫了幾篇了,但一直沒有以channel爲主題進行介紹,今天就給你們聊一聊channel,channel的基本使用很是簡單,想必你們都已瞭解,因此直接來個進階點的:介紹channel的阻塞狀況,以及給你一個必殺技,立馬解決阻塞問題,實用性高git

阻塞場景

不管是有緩存通道、無緩衝通道都存在阻塞的狀況。阻塞場景共4個,有緩存和無緩衝各2個。github

無緩衝通道的特色是,發送的數據須要被讀取後,發送纔會完成,它阻塞場景golang

  1. 通道中無數據,但執行讀通道。
  2. 通道中無數據,向通道寫數據,但無協程讀取。
// 場景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註釋表明函數的執行結果,每個函數都因爲阻塞在通道操做而沒法繼續向下執行,最後報了死鎖錯誤。緩存

有緩存通道的特色是,有緩存時能夠向通道中寫入數據後直接返回,緩存中有數據時能夠從通道中讀到數據直接返回,這時有緩存通道是不會阻塞的,它阻塞場景是併發

  1. 通道的緩存無數據,但執行讀通道。
  2. 通道的緩存已經佔滿,向通道寫數據,但無協程讀。
// 場景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實現無阻塞讀寫

select是執行選擇操做的一個結構,它裏面有一組case語句,它會執行其中無阻塞的那一個,若是都阻塞了,那就等待其中一個不阻塞,進而繼續執行,它有一個default語句,該語句是永遠不會阻塞的,咱們能夠藉助它實現無阻塞的操做。若是不瞭解,不想多瞭解一下select能夠先看下這2篇文章:less

下面示例代碼是使用select修改後的無緩衝通道和有緩衝通道的讀寫,如下函數能夠直接經過main函數調用,其中的Ouput的註釋是運行結果,從結果能看出,在通道不可讀或者不可寫的時候,再也不阻塞等待,而是直接返回。函數

// 無緩衝通道讀
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結構實現通道讀
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結構實現通道寫
func WriteChWithSelect(ch chan int) error {
    select {
    case ch <- 1:
        return nil
    default:
        return errors.New("channel blocked, can not write")
    }
}

使用Select+超時改善無阻塞讀寫

使用default實現的無阻塞通道阻塞有一個缺陷:當通道不可讀或寫的時候,會便可返回。實際場景,更多的需求是,咱們但願嘗試讀一會數據,或者嘗試寫一會數據,若是實在無法讀寫再返回,程序繼續作其它的事情。spa

使用定時器替代default能夠解決這個問題,給通道增長讀寫數據的容忍時間,若是500ms內沒法讀寫,就即刻返回。示例代碼修改一下會是這樣:code

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")
    }
}

結果就會變成超時返回:協程

read time out
write time out
read time out
write time out

示例源碼

本文全部示例源碼,及歷史文章、代碼都存儲在Github:https://github.com/Shitaibin/golang_step_by_step/tree/master/channel/unblock_channel

這篇文章了channel的阻塞狀況,以及解決阻塞的2種辦法:

  1. 使用select的default語句,在channel不可讀寫時,便可返回
  2. 使用select+定時器,在超時時間內,channel不可讀寫,則返回

但願這篇文章對你的channel讀寫有所啓發。

  1. 若是這篇文章對你有幫助,請點個贊/喜歡,感謝
  2. 本文做者:大彬
  3. 若是喜歡本文,隨意轉載,但請保留此原文連接:http://lessisbetter.site/2018/11/03/Golang-channel-read-and-write-without-blocking/

一塊兒學Golang-分享有料的Go語言技術

相關文章
相關標籤/搜索