Ivan
Ivan

Reputation: 13

Why doesn't this buffered channel block in my code?

I'm learning the Go language. Can someone please explain the output here?

package main

import "fmt"

var c = make(chan int, 1)

func f() {

    c <- 1

    fmt.Println("In f()")
}

func main() {
    go f()

    c <- 2
    fmt.Println(<-c)
    fmt.Println(<-c)

}

Output:

In f()
2
1

Process finished with exit code 0

Why did "In f()" occur before "2"? If "In f()" is printed before "2", the buffered channel should block. But this didn't happen, why?

The other outputs are reasonable.

Image of my confusing result

Upvotes: 0

Views: 119

Answers (2)

Ulrich Eckhardt
Ulrich Eckhardt

Reputation: 17415

The order of events that cause this to run through are these:

  1. Trigger goroutine.
  2. Write 2 to the channel. Capacity of the channel is now exhausted.
  3. Read from the channel and output result.
  4. Write 1 to the channel.
  5. Read from the channel and output result.

The order of events that cause this to deadlock are these:

  1. Trigger goroutine.
  2. Write 1 to the channel. Capacity of the channel is now exhausted.
  3. Write 2 to the channel. Since the channel's buffer is full, this blocks.

The output you provide seems to indicate that the goroutine finishes first and the program doesn't deadlock, which contradicts above two scenarios as explanation. Here's what happens though:

  1. Trigger goroutine.
  2. Write 2 to the channel.
  3. Read 2 from the channel.
  4. Write 1 to the channel.
  5. Output In f().
  6. Output the 2 received from the channel.
  7. Read 1 from the channel.
  8. Output the 1 received from the channel.

Keep in mind that you don't have any guarantees concerning the scheduling of goroutines unless you programmatically enforce them. When you start a goroutine, it is undefined when the first code of that goroutine are actually executed and how much the starting code progresses until then. Note that since your code relies on a certain order of events, it is broken by that definition, just to say that explicitly.

Also, at any point in your program, the scheduler can decide to switch between different goroutines. Even the single line fmt.Printtln(<-c) consists of multiple steps and in between each step the switch can occur.

Upvotes: 2

Sergii Getman
Sergii Getman

Reputation: 4371

To reproduce block you have to run that code many times the easiest way to do it with test. You don't have block cause of luck. But it is not guaranteed:

var c = make(chan int, 1)

func f() {

    c <- 1

    fmt.Println("In f()")
}

func TestF(t *testing.T) {
    go f()

    c <- 2
    fmt.Println(<-c)
    fmt.Println(<-c)

}

and run with command:

go test -race -count=1000 -run=TestF -timeout=4s

where count number of tests. It reproduces blocking to me. Provide timeout to not wait default 10 minutes

Upvotes: 1

Related Questions