Write to channel blocked forever

Issue

I am stuck in a strange situation where the write operation to the channel never happens.

package main

import (
    "fmt"
    "time"
)

func main() {
    c := make(chan int)
    s := make(chan bool)
    k := make(chan bool)
    fmt.Println("start")
    go func() {
    fmt.Println("routine 1")
        s <- true
    }()
    go func() {
    fmt.Println("routine 2")
        for {
            select {
            case  <-s :
                for i := 0; i < 3; i++ {
                    fmt.Println("before")
                    c <- i
                    fmt.Println("after")
                }       
            case <- k :
                fmt.Println("k ready")
                break
            }
        }
    }()

    go func() {
        fmt.Println("routine 3")
        for {
            select {
                case x := <- c :
                fmt.Println("x=", x)
                k <- true
                fmt.Println("k done")

            }
        }
    }()

    time.Sleep(1000 * time.Millisecond)
}

And here is the output:

start
routine 1
routine 2
before
routine 3
x= 0
after
before

I wonder why the write to the channel k blocks, but the log statement fmt.Println("k ready") is never printed.

Here is what I think :

  • go routine 1 writes true to channel s
  • go routine 2 writes 0 to
    channel c and waits because buffer size is 0, it will not be able to
    write ‘1’ to it unless someone reads channel c
  • go routine 3 comes into the picture, reads channel c (now go routine 2
    can write to c once go routine 2 resumes) prints the value of x. NOW IT SHOULD BE ABLE TO WRITE TO CHANNEL K but that is not happening

According to me it should be able to write to channel k then case 2 of goroutine should execute and print “k ready”

Can anyone explain me why write to the channel blocked?
As a fix I know I can increase the buffer size of channel c and everything will get printed but I am not interested in fixing this, instead I want to understand this scenario.

A nice blog to understand the above case.

Solution

You have a deadlock.

  • goroutine 1 writes to s then quits
  • goroutine 2 reads from s, and writes to c
  • goroutine 3 reads from c, and writes to k, and this blocks because nothing is reading from k, because goroutine 2 is blocked in the write to k above.
  • goroutine 2 writes to c again which blocks as goroutine 3 is still trying to write to k and thus is not reading from c

Contrary to what you say, you don’t have a buffer size of 1. You have a buffer size of zero (i.e. an unbuffered channel), so a write will block until something reads. This is probably the source of your misunderstanding. Per the language specification:

A new, initialized channel value can be made using the built-in function make, which takes the channel type and an optional capacity as arguments:

make(chan int, 100)

The capacity, in number of elements, sets the size of the buffer in the channel. If the capacity is zero or absent, the channel is unbuffered and communication succeeds only when both a sender and receiver are ready. Otherwise, the channel is buffered and communication succeeds without blocking if the buffer is not full (sends) or not empty (receives). A nil channel is never ready for communication.

Answered By – abligh

Answer Checked By – Candace Johnson (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.