Go - Channel 原理

注:該文原文爲 Channel Axioms ,做者是 Dave Cheney,這是他的博客地址程序員

大部分的新的 Go 程序員能快速理解 channel 是做爲一個 queue 的值和認同當 channel 是滿的或者是空的時候, 操做是阻塞的概念。golang

這篇文章探討了 channel 四個不太常見的特性:c#

  • 給一個 nil channel 發送數據,形成永遠阻塞
  • 從一個 nil channel 接收數據,形成永遠阻塞
  • 給一個已經關閉的 channel 發送數據,引發 panic
  • 從一個已經關閉的 channel 接收數據,當即返回一個零值

給一個 nil channel 發送數據,形成永遠阻塞

這第一個例子對於新來者是有點小驚奇的,它給一個 nil channel 發送數據,形成永遠阻塞。函數

如下這個程序將在第5行形成死鎖,由於未初始化的 channel 是 nil 的,其值是.net

package main

func main() {
        var c chan string
        c <- "let's get started" // deadlock
}

點擊這裏運行code

從一個 nil channel 接收數據,形成永遠阻塞

相似的,從一個 nil channel 接收數據,會形成接受者永遠阻塞。blog

package main

import "fmt"

func main() {
        var c chan string
        fmt.Println(<-c) // deadlock
}

點擊這裏運行ip

爲何會發生這樣的狀況?下面是一個可能的解釋get

  • channel 的 buffer 的大小不是類型聲明的一部分,所以它必須是 channel 的值的一部分
  • 若是 channel 未被初始化,它的 buffer 的大小將是0
  • 若是 channel 的 buffer 大小是0,那麼它將沒有 buffer
  • 若是 channel 沒有 buffer,一個發送將會被阻塞,直到另一個 goroutine 爲接收作好了準備
  • 若是 channel 是 nil 的,而且接收者和發送者沒有任何交互,他們都會阻塞而後在各自的 channel 中等待以及再也不被解除阻塞狀態

給一個已經關閉的 channel 發送數據,引發 panic

如下程序將有可能 panic,由於在它的兄弟姐妹有時間完成發送他們的值以前,這第一個 goroutine 在達到10的時候將關閉 channel。博客

package main

import "fmt"

func main() {
        var c = make(chan int, 100)
        for i := 0; i < 10; i++ {
                go func() {
                        for j := 0; j < 10; j++ {
                                c <- j
                        }
                        close(c)
                }()
        }
        for i := range c {
                fmt.Println(i)
        }
}

點擊這裏運行

所以爲何沒有一個 close() 版本能讓你檢測 channel 是否關閉?

if !isClosed(c) {
        // c isn't closed, send the value
        c <- v
}

可是這個函數有一個內在的競爭,某我的可能在咱們檢查完 isClosed(c) 以後,可是代碼獲取 c <- v 以前關閉這個 channel。

處理這個問題的方法在被鏈接在該文章底部的 2nd article 被討論。

從一個已經關閉的 channel 接收數據,當即返回一個零值

這最後一個示例與前一個是相反的,一旦一個 channel 被關閉,它的全部的值都會從 buffer 中流失,channel 將當即返回0值。

package main

import "fmt"

func main() {
            c := make(chan int, 3)
            c <- 1
            c <- 2
            c <- 3
            close(c)
            for i := 0; i < 4; i++ {
                        fmt.Printf("%d ", <-c) // prints 1 2 3 0
            }
}

點擊這裏運行

針對這個問題的正確的解決辦法是使用 range 循環處理:

for v := range c {
            // do something with v
}

for v, ok := <- c; ok ; v, ok = <- c {
            // do something with v
}

這兩個語句在函數中是相等的,展現 range 是作什麼。

擴展閱讀

相關文章
相關標籤/搜索