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

huangapple go评论95阅读模式
英文:

Go: range receiving only odd number of values from channel

问题

我正在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  // 取消注释以发送更多值
//  c <- 6
    close(c)
}

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

如果我发送偶数个值(比如1、2、3和4),最后一个值就不会被打印出来。

似乎通道创建行:

c := make(chan int)

当我添加不同大小的缓冲区时,会改变range表达式的行为:

(我发送了4个值)

c := make(chan int)     // 打印1,2,3
c := make(chan int, 1)  // 相同的行为,打印1,2,3
c := make(chan int, 2)  // 打印1,2
c := make(chan int, 3)  // 打印1,2,3
c := make(chan int, 4)  // [没有输出]
c := make(chan int, 5)  // [没有输出]
c := make(chan int, 20) // [没有输出]

为什么当我发送偶数个值时,它不接收最后一个值?


更多内容:

我也在离线环境下进行了测试,在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这一行会阻塞主函数,直到所有通道都被读取完。

英文:

I am running this code in the sandbox in http://tour.golang.org/

I thought that once I launched the goroutine that ranges over the channel, all values I would send through would be printed.

package main

import &quot;fmt&quot;

func main() {
    c := make(chan int)
    
    go (func(c chan int){
        for v := range c {
            fmt.Println(v)
        }
    })(c)
    
    c &lt;- 1
    c &lt;- 2
    c &lt;- 3
    c &lt;- 4   
//  c &lt;- 5  // uncomment to send more values
//  c &lt;- 6
    close(c)
}

But if I send an odd number of values (say, 1, 2 and 3), all values get printed.

If I send an even number of values(say, 1, 2, 3 and 4), the last one does not get printed.

It seems that the channel creation line:

    c := make(chan int)

Changes the behavior of the range expression when I add a buffer of different sizes:

(I am sending 4 values)

    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]

Why is it not receiving the last value when I send an even number?


More in this:

I tested this offline also, compiling under 64 bit Linux.

I changed the program a little:

package main

import (
    &quot;fmt&quot;
)

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 &lt;- 1
    c &lt;- 2
    c &lt;- 3
    c &lt;- 4
//  c &lt;- 5
//  c &lt;- 6
    cc &lt;- 1000
//  cc &lt;- 2000
    close(c)
    close(cc)
}

If I decomment the line cc &lt;- 2000 then everything gets printed. But if I leave it commented out, I only get 1, 2 and 3.

Seems like a timing issue. I thought that the line cc &lt;- 1000 would block the main function until all channels get read.

答案1

得分: 4

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

当主函数结束时,所有其他的goroutine也会结束。

goroutine内部的for循环不是原子操作。
cc &lt;- 1000这一行确实会阻塞主函数,但是range本身会解锁它,导致主函数终止(同时也终止了goroutine),不允许fmt.Println执行。

英文:

Writing the last line I think I realised what the problem was.

When main ends all other goroutines end.

The for loop inside the goroutine is not atomic.
The line cc &lt;- 1000 DOES block main, but the range itself unlocks it, and main dies (and kills the goroutine too) not allowing fmt.Println to execute.

答案2

得分: 4

根据内存模型,你认为关闭通道更像是发送而不是实际情况。

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

因此,你可以确保这些关闭语句将在它们对应的循环终止之前完成。由于你还知道关闭语句必须在这些通道上的最后一次发送之后发生(因为它们在同一个go例程中),所以你知道除了最后一个发送的值之外,其他所有值都将被保证打印出来。我认为你期望的是关闭操作更像是发送操作,这样循环就会强制打印出最后一个值。如果你将关闭语句替换为发送-1的操作,例如,这将确保所有的值(除了可能的-1)都会被打印出来。无法保证是否会打印出-1。

显然,这只是对某个问题的简化,但我认为编写你的示例的“正确”方法是使用sync.WaitGroup。它非常简单,非常适合启动多个go例程并等待它们全部完成。以下是使用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()
}
英文:

You are thinking of a close as being more like a send than it is, according to the memory model:

> The closing of a channel happens before a receive that returns a zero
> value because the channel is closed.

So you are guaranteed that those close statements will complete before their corresponding loops terminate. Since you also know the close statements must happen after the last send on those channels (since they are in the same go-routine) you know that all but the last value sent on them will be guaranteed to print. I think what you were expecting was the close to act more like a send, so that the loop is forced to print its last value. If you replace the close statements with a send of -1, for example, this will guarantee all of the values (except the -1's, possibly) will get printed. Whether or not the -1's get printed is not guaranteed.

Obviously this is a simplification of something, but I think the 'proper' way to write your example would be to use a sync.WaitGroup. It's very easy and is perfect for firing off several go-routines and waiting for them all to complete. Here is your code re-written to use a WaitGroup:

package main

import (
  &quot;fmt&quot;
  &quot;sync&quot;
)

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 &lt;- 1
  c &lt;- 2
  c &lt;- 3
  c &lt;- 4
  cc &lt;- 1000
  cc &lt;- 2000
  close(c)
  close(cc)
  wg.Wait()
}

huangapple
  • 本文由 发表于 2012年7月19日 22:46:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/11563363.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定