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當成單向輸入管道來使用了

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

Blocking

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

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

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

輸出結果如下

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

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

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

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就是關閉的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章