Search code examples
gogoroutinechannel

Why does Go Channel's buffer not limiting writing/reading correctly?


I am trying to use channels to communicate between two go routines. At first I created the channel of integers, then I passed it as parameter to a go routine which prints a sequence of numbers from 0 to 10. The outputs of those programs are not making sense.

This is the main code:

func Worker(identifier string, ch chan<- int) {
    fmt.Printf("Entering worker %s\n", identifier)
    for i := 0; i < 10; i++ {
        fmt.Printf("Writing %d\n", i)
        ch <- i
    }

    fmt.Printf("Exiting worker %s\n", identifier)

    close(ch)
}

func main() {
    ch := make(chan int)
    go Worker("1", ch)

    for v := range ch {
        fmt.Printf("Reading %d\n", v)
    }
}

For that code execution I got this output:

Entering worker 1
Writing 0
Writing 1
Reading 0
Reading 1
Writing 2
Writing 3
Reading 2
Reading 3
Writing 4
Writing 5
Reading 4
Reading 5
Writing 6
Writing 7
Reading 6
Reading 7
Writing 8
Writing 9
Reading 8
Reading 9
Exiting worker 1

Please, note that there are two writing executions then two reading executions.

Late on, I set a buffer size to make function like this:

func main() {
    ch := make(chan int, 3) // <= Buffer
    go Worker("1", ch)

    for v := range ch {
        fmt.Printf("Reading %d\n", v)
    }

}

And then, got this output:

Entering worker 1
Writing 0
Writing 1
Writing 2
Writing 3
Writing 4
Reading 0
Reading 1
Reading 2
Reading 3
Reading 4
Writing 5
Writing 6
Writing 7
Writing 8
Writing 9
Reading 5
Reading 6
Reading 7
Reading 8
Reading 9
Exiting worker 1

Please, note that now we have 5 writing execution then 5 reading execution.

Once we have the code and outputs, there goes the final question: Why did those executions behave like this? In the first, wasn't it supposed to read and write only one number per time? Beyond that, why the second execution reads and write 5 numbers per time instead of 3 (since this is the buffer size)?


Solution

  • You're mixing up when the messages are printed and when the numbers are read from or written to the channel.

    "Writing" messages don't happen when a write happens. They happen at some point between writes. Similarly, "Reading" messages happen at some point between reads.

    Here's one way your first snippet could have gotten scheduled, that would have produced the displayed output:

    • main attempts to read, and blocks.
    • Worker prints "Writing 0".
    • Worker writes 0, which main reads.
    • Worker prints "Writing 1".
    • Worker attempts to write 1, and blocks.
    • main prints "Reading 0".
    • main reads 1.
    • main prints "Reading 1".
    • main attempts to read, and blocks.

    Control keeps passing between main and Worker like this, each printing 2 messages before blocking.

    Similarly, your second snippet could have been scheduled like this:

    • main attempts to read, and blocks.
    • Worker prints "Writing 0", and sends a 0 straight to main.
    • Worker prints "Writing 1", and buffers a 1.
    • Worker prints "Writing 2", and buffers a 2.
    • Worker prints "Writing 3", and buffers a 3.
    • Worker prints "Writing 4", and blocks trying to send a 4.
    • main finishes the read it was blocked on, and prints "Reading 0".
    • main reads the buffered 1, and prints "Reading 1".
    • main reads the buffered 2, and prints "Reading 2".
    • main reads the buffered 3, and prints "Reading 3".
    • main reads the 4 that Worker was blocked on, and prints "Reading 4".
    • main attempts to read, and blocks.
    • Execution returns to Worker...