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 是作什么。

扩展阅读

相关文章
相关标签/搜索