如何使用频道?

时间:2019-02-14 06:27:47

标签: go

我有一个接受一个int数组并将其转储到通道中的函数。

func Dump(a []int, ch chan int) {
    for i := range a {
        ch <- i
    }
    close(ch)
}

此主要版本未构建:

func main() {
    ch := make(chan int)
    arr := []int{1, 2, 3, 4, 5}
    Dump(arr, ch)
    for i := range ch {
        fmt.Printf("Got %v\n", i)
    }
}

引发此错误:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.Dump(0xc000078f48, 0x5, 0x5, 0xc00006e060)
        /Users/300041738/go-workspace/src/test.go:7 +0x43
main.main()
        /Users/300041738/go-workspace/src/test.go:15 +0x9b
exit status 2

但是,这会建立:

func main() {
    ch := make(chan int)
    arr := []int{1, 2, 3, 4, 5}
    go Dump(arr, ch)
    for i := range ch {
        fmt.Printf("Got %v\n", i)
    }
}

为什么我必须在转储前写go?我不希望异步转储数组的内容。

3 个答案:

答案 0 :(得分:3)

通道具有缓冲区。默认情况下,缓冲区的大小为0。换句话说,如果要将元素插入到非缓冲通道中,则插入的goroutine将暂停,直到另一个goroutine从通道中检索值为止。

所以很有趣,试试这个:

import "fmt"

func Dump(a []int, ch chan int) {
    for i := range a {
        ch <- i
    }
    close(ch)
}

func main() {
    arr := []int{1, 2, 3, 4, 5}
    ch := make(chan int, len(arr)) //specify channel buffer length equal to arr size
    Dump(arr, ch)
    for {
        i, ok := <- ch
        if ok {
            fmt.Println("received a number !", i)
        } else {
            fmt.Println("channel is closed, we're done here")
        }
    }
}

答案 1 :(得分:2)

引起Dealock的原因是您的主要goroutine试图写入通道,但没有人从该通道读取数据。您无需在此处使用缓冲通道。

有一个流水线概念。您的Dump函数基本上就像这里的管道源一样。您可以将Dump函数修改为如下形式:

func Dump(a []int) chan int {
  ch := make(chan int)
  go func() {
    for i := range a {
      ch <- i
    }
    close(ch)
  }()
  return ch
}

请注意,我现在正在单独的go例程中写入通道。

答案 2 :(得分:1)

问题的根源-当您写入无缓冲通道时,该通道将被锁定,直到有人从该通道读取值为止。 对于无缓冲通道-在每次写操作之后,您必须执行一次读操作。否则,您的第二次写操作将被锁定,直到有人读取了第一个值。在您的第一个示例中,它永远不会发生。

说明:

  1. Dump()调用
  2. 运行for循环的第一次迭代
  3. i值写入通道
  4. 运行for循环的第二次迭代
  5. 试图将下一个i值写入通道,但锁定,没有人读取第一个值。
  6. 由于所有工作都在单个主goroutine中完成,因此整个应用程序都被锁定。死锁结果。

在添加goroutine用法(go Dump(arr, ch))时会发生这种情况:

    在单独的goroutine中调用
  1. Dump()函数
  2. 运行for循环的第一次迭代
  3. i值写入通道
  4. 运行for循环的第二次迭代
  5. 试图将下一个i值写入通道,但锁定,没有人读取第一个值。
  6. Goroutine已锁定(我们在其中运行Dump()的地方),但是我们拥有主goroutine,而go调度程序将控件切换到了主goroutine。
  7. 这意味着该行实际上已执行for i := range ch。并最终读取第一个值!
  8. 现在Dump() goroutine已解锁,可以写入第二个值了。
  9. 控件将在两个goroutine之间传递,直到完成所有工作。

请注意,执行的确切顺序可能有所不同(基于go调度程序的逻辑)。要使用它,您可以使用Dump()将打印添加到go Dump(arr, ch)函数中:

func Dump(a []int, ch chan int) {
    for i := range a {
        fmt.Printf("Write %v\n", i)
        ch <- i
    }
    close(ch)
}

您将看到Write和Got消息会混合在一起。

很难为您的答案提供解决方案,因为它是一个沙盒示例,实际上并不需要该通道。

通过使用缓冲通道,假设大小为n,您可以进行n写操作而不会锁定和读取。 我建议您阅读basics of Go channels