Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go: range receiving only odd number of values from channel

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.

like image 740
Sebastián Grignoli Avatar asked Dec 10 '25 20:12

Sebastián Grignoli


1 Answers

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()
}
like image 200
Running Wild Avatar answered Dec 12 '25 16:12

Running Wild