3

我在http://tour.golang.org/的沙箱中运行此代码

我认为一旦我启动了覆盖通道的 goroutine,我将发送的所有值都会被打印出来。

package main

import "fmt"

func main() {
    c := make(chan int)

    go (func(c chan int){
        for v := range c {
            fmt.Println(v)
        }
    })(c)

    c <- 1
    c <- 2
    c <- 3
    c <- 4   
//  c <- 5  // uncomment to send more values
//  c <- 6
    close(c)
}

但是如果我发送奇数个值(比如 1、2 和 3),所有值都会被打印出来。

如果我发送偶数个值(例如 1、2、3 和 4),则不会打印最后一个值。

似乎频道创建行:

    c := make(chan int)

当我添加不同大小的缓冲区时更改范围表达式的行为:

(我发送 4 个值)

    c := make(chan int)     // prints 1,2,3
    c := make(chan int, 1)  // same behavior, prints 1,2,3
    c := make(chan int, 2)  // prints 1,2
    c := make(chan int, 3)  // prints 1,2,3
    c := make(chan int, 4)  // [no output]
    c := make(chan int, 5)  // [no output]
    c := make(chan int, 20) // [no output]

为什么我发送偶数时没有收到最后一个值?


更多内容:

我也离线测试了这个,在 64 位 Linux 下编译。

我稍微改变了程序:

package main

import (
    "fmt"
)

func main() {
    c := make(chan int)
    cc := make(chan int)

    p := func (c chan int){
      for v := range c {
        fmt.Println(v)
      }
    }

    go p(c)
    go p(cc)

    c <- 1
    c <- 2
    c <- 3
    c <- 4
//  c <- 5
//  c <- 6
    cc <- 1000
//  cc <- 2000
    close(c)
    close(cc)
}

如果我取消该行cc <- 2000,那么所有内容都会被打印出来。但如果我把它注释掉,我只会得到 1、2 和 3。

好像是时间问题。我认为该行cc <- 1000会阻塞主要功能,直到所有通道都被读取。

4

2 回答 2

4

根据内存模型,您认为关闭更像是发送而不是发送:

通道的关闭发生在接收返回零值之前,因为通道已关闭。

因此,您可以保证这些关闭语句将在其相应的循环终止之前完成。由于您还知道关闭语句必须在这些通道上的最后一次发送之后发生(因为它们在同一个 go-routine 中),您知道除了最后发送的值之外的所有值都将保证打印。我认为您所期望的是关闭更像是发送,因此循环被迫打印其最后一个值。例如,如果您将关闭语句替换为 -1 的发送,这将保证所有值(可能除了 -1 之外)都将被打印。不能保证是否打印 -1。

显然这是对某些事情的简化,但我认为编写示例的“正确”方法是使用sync.WaitGroup。它非常简单,非常适合启动几个 go-routines 并等待它们全部完成。这是您重新编写的代码以使用 WaitGroup:

package main

import (
  "fmt"
  "sync"
)

func main() {
  c := make(chan int)
  cc := make(chan int)

  var wg sync.WaitGroup

  p := func(c chan int) {
    for v := range c {
      fmt.Println(v)
    }
    wg.Done()
  }

  wg.Add(2)
  go p(c)
  go p(cc)

  c <- 1
  c <- 2
  c <- 3
  c <- 4
  cc <- 1000
  cc <- 2000
  close(c)
  close(cc)
  wg.Wait()
}
于 2012-07-20T22:33:54.523 回答
3

写到最后一行,我想我意识到了问题所在。

当 main 结束时,所有其他 goroutine 都结束。

forgoroutine 内部的循环不是原子的。该行cc <- 1000确实阻塞了 main,但它range本身将其解锁,并且 main 死了(并且也杀死了 goroutine)不允许fmt.Println执行。

于 2012-07-19T14:48:52.020 回答