Go學習之Channel總結

Channel是Go中的一個核心類型,你能夠把它當作一個管道,經過它併發核心單元就能夠發送或者接收數據進行通信(communication)。面試

類型

T表示任意的一種類型併發

  • 雙向: chan T
  • 單向僅發送: chan <-
  • 單向僅接受: <- chan

單向的channel,不只能夠經過聲明make(chan <- interface{}) 來建立,還能夠經過隱身或顯示的經過 chan 來轉換,以下函數

func main() {
      channel := make(chan int, 10)
        convert(channel)
}
func convert(channel chan<- int) {}

convert函數中,就能夠吧channel當成單向輸入管道來使用了code

既然 雙向 chan,既能夠接收,也能夠發送,爲何還會有單向chan的存在? 個人一個理解即是 權限收斂,例如一個爬蟲系統中,有些進程a僅僅負責抓取頁面內容,並轉發給進程b,那進程a僅須要 單向發送的chan 便可進程

Blocking

缺省狀況下,發送chan或接收chan會一直阻塞着,直到另外一方準備好。這種方式能夠用來在gororutine中進行同步,而沒必要使用顯示的鎖或者條件變量。同步

如官方的例子中x, y := <-c, <-c這句會一直等待計算結果發送到channel中。如下面例子看一下io

func bufferChannel() {
    channel := make(chan int)
    i := 0
    go func(i int) {
    fmt.Printf("start goroutine %d\n", i)①
        channel <- i
        fmt.Printf("send %d to channel\n", i)②
    }(i)
    time.Sleep(2 * time.Second)
    fmt.Println("sleep 2 second")
    value := <-channel③
    fmt.Println("got ", value)
}

輸出結果以下class

start goroutine 0
sleep 2 second
got  0
send 0 to channel

能夠看出,go func 執行到了①後並無繼續執行②,而是等待③執行完成後,再去執行②,也就能夠說明 channel <- i 阻塞了goroutine的繼續執行變量

若是,我不想在這裏阻塞,而是我直接把數據放到channel裏,等接收方準備好後,到channel中自取自用如何處理,這裏就涉及到了另外一個概念 buffered channelsed

buffered channel

咱們把程序修改一下

func bufferChannel() {
    channel := make(chan int, 1) // 這裏加了個參數
    i := 0
    go func(i int) {
        fmt.Printf("start goroutine %d\n", i)①
        channel <- i
        fmt.Printf("send %d to channel\n", i)②
    }(i)
    time.Sleep(2 * time.Second)
    fmt.Println("sleep 2 second")
    value := <-channel③
    fmt.Println("got ", value)
}

輸出結果

start goroutine 0
send 0 to channel
sleep 2 second
got  0

咱們發現go func執行完①以後就執行了②,並無等待③的執行結束,這就是buffered channel的效果了

咱們只須要在make的時候,聲明底2個參數,也就是chan的緩衝區大小便可

經過上面的程序能夠看出,咱們一直在使用③的造成,即<- chan來讀取chan中的數據,可是若是有多個goroutine在同時像一個chan寫數據,咱們除了使用

for {
    value <- chan
}

還有什麼更優雅的方式嗎

for … range

仍是上面那個程序,咱們使用 for … range 進行一下改造

func bufferChannel() {
   channel := make(chan int, 1)
   i := 0
   go func(i int) {
      fmt.Printf("start goroutine %d\n", i)
      channel <- i
      fmt.Printf("send %d to channel\n", i)
   }(i)
   time.Sleep(2 * time.Second)
   fmt.Println("sleep 2 second")
   for value := range channel {
      fmt.Println("got ", value)
   }
}

這樣就能夠遍歷 channel 中的數據了,可是咱們在運行的時候就會發現,哎 這個程序怎麼停不下來了?range channel產生的迭代值爲Channel中發送的值,它會一直迭代直到channel被關閉,因此 咱們goroutine發送完數據後,把channel關閉一下試試,這一次,咱們再也不進行time.Sleep(2 * time.Second)

func bufferChannel() {
   channel := make(chan int, 1)
   i := 0
   go func(i int) {
      fmt.Printf("start goroutine %d\n", i)
      channel <- i
      fmt.Printf("send %d to channel\n", i)
      close(channel)
   }(i)
   for value := range channel {
      fmt.Println("got ", value)
   }
}

這樣,整個程序就能夠正常退出了,因此,在使用range的時候須要注意,若是channel不關閉,則range會一直阻塞在這裏的

select

咱們上面講的一直都是隻有一個channel的時候,咱們應該怎麼去作,加入有兩個channel或者更多的channel,咱們應該怎麼去作,這裏就介紹一下 go裏面的多路複用 select,如下面程序爲例

func example() {
    tick := time.Tick(time.Second)
    after := time.After(3 * time.Second)
    for {
        select {
        case <-tick:
            fmt.Println("tick 1 second")
        case <-after:
            fmt.Println("after 3 second")
            return
    default:
            fmt.Println("come into default")
            time.Sleep(500 * time.Millisecond)
        }
    }
}

time.Tick是go的time包提供的一個定時器的一個函數,它返回一個channel,並在指定時間間隔內,向channel發送一條數據,time.Tick(time.Second)就是每秒鐘向這個channel發送一個數據

time.After是go的time包提供的一個定時器的一個函數,它返回一個channel,並在指定時間間隔後,向channel發送一條數據,time.After(3 * time.Second)就是3s後向這個channel發送一個數據

輸出結果

come into default
come into default
tick 1 second
come into default
come into default
tick 1 second
come into default
come into default
tick 1 second
after 3 second

能夠看到,select會選擇一個沒有阻塞的 channel,並執行響應 case下的邏輯,這樣就能夠避免因爲一個 channel阻塞而致使後續的邏輯阻塞的狀況了

咱們繼續作個小實驗,把上面關閉的channel放到 select裏面試一下

func example() {
    tick := time.Tick(time.Second)
    after := time.After(3 * time.Second)
    channel := make(chan int, 1)
    go func() {
        channel <- 1
        close(channel)
    }()
    for {
        select {
        case <-tick:
            fmt.Println("tick 1 second")
        case <-after:
            fmt.Println("after 3 second")
            return
        case value := <- channel:
            fmt.Println("got", value)
        default:
            fmt.Println("come into default")
            time.Sleep(500 * time.Millisecond)
        }
    }
}

輸出結果

.
.
.
.
got 0
got 0
got 0
got 0
got 0
after 3 second

簡直是車禍現場,幸虧設置了3s主動退出,那case的時候,有沒有辦法判斷這個channel是否關閉了呢,固然是能夠的,看下面的程序

func example() {
    tick := time.Tick(time.Second)
    after := time.After(3 * time.Second)
    channel := make(chan int, 1)
    go func() {
        channel <- 1
        close(channel)
    }()
    for {
        select {
        case <-tick:
            fmt.Println("tick 1 second")
        case <-after:
            fmt.Println("after 3 second")
            return
        case value, ok := <- channel:
            if ok {
                fmt.Println("got", value)
            } else {
                fmt.Println("channel is closed")
                time.Sleep(time.Second)
            }
        default:
            fmt.Println("come into default")
            time.Sleep(500 * time.Millisecond)
        }
    }
}

輸出結果

come into default
got 1
channel is closed
tick 1 second
channel is closed
channel is closed
after 3 second

綜上能夠看出,經過 value, ok := <- channel 這種形式,ok獲取的就是用來判斷channel

是否關閉的,ok爲 true,表示channel正常,不然,channel就是關閉的

相關文章
相關標籤/搜索