Go:范围仅从通道接收奇数个值

时间:2012-07-19 14:46:21

标签: range go channel goroutine

我在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将阻止主函数,直到所有通道都被读取。

2 个答案:

答案 0 :(得分:4)

根据memory model,您正在考虑接近发送而不是发送:

  

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

因此,您可以确保在相应的循环终止之前完成这些紧密语句。因为你也知道必须在最后一次发送这些通道之后发生关闭语句(因为它们在同一个例行程序中),你知道除了最后发送的值之外的所有值都将保证打印。我认为你所期待的是接近行为更像是一个发送,所以循环被迫打印它的最后一个值。例如,如果用发送值-1替换close语句,这将保证将打印所有值(可能除-1之外)。是否打印-1不能得到保证。

显然这是对某些内容的简化,但我认为编写示例的“正确”方法是使用sync.WaitGroup。它非常简单,非常适合解雇几个常规程序并等待它们全部完成。以下是您重写的代码以使用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()
}

答案 1 :(得分:3)

写下最后一行我认为我意识到问题所在。

当主要结束时,所有其他goroutine结束。

goroutine中的for循环不是原子的。 第cc <- 1000行阻止主要,但range本身解锁它,主要死亡(并杀死goroutine)不允许fmt.Println执行。