質問

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 "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)
}

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 (
    "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)
}

If I decomment the line cc <- 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 <- 1000 would block the main function until all channels get read.

役に立ちましたか?

解決

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 (
  "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()
}

他のヒント

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 <- 1000 DOES block main, but the range itself unlocks it, and main dies (and kills the goroutine too) not allowing fmt.Println to execute.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top