67

I'm sure that there is a simple explanation to this trivial situation, but I'm new to the go concurrency model.

when I run this example

package main

import "fmt"

func main() {
    c := make(chan int)    
    c <- 1   
    fmt.Println(<-c)
}

I get this error :

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
    /home/tarrsalah/src/go/src/github.com/tarrsalah/tour.golang.org/65.go:8 +0x52
exit status 2

Why ?


Wrapping c <- in a goroutine makes the example run as we expected

package main

import "fmt"

func main() {
    c := make(chan int)        
    go func(){
       c <- 1
    }()
    fmt.Println(<-c)
}

Again, why ?

Please, I need deep explanation , not just how to eliminate the deadlock and fix the code.

030
  • 10,842
  • 12
  • 78
  • 123
Salah Eddine Taouririt
  • 24,925
  • 20
  • 60
  • 96
  • 1
    What happens when you call your phone number from the same phone number? Deadlock or busy tone etc. It's the same thing here. – Inanc Gumus Nov 15 '19 at 15:10

4 Answers4

108

From the documentation :

If the channel is unbuffered, the sender blocks until the receiver has received the value. If the channel has a buffer, the sender blocks only until the value has been copied to the buffer; if the buffer is full, this means waiting until some receiver has retrieved a value.

Said otherwise :

  • when a channel is full, the sender waits for another goroutine to make some room by receiving
  • you can see an unbuffered channel as an always full one : there must be another goroutine to take what the sender sends.

This line

c <- 1

blocks because the channel is unbuffered. As there's no other goroutine to receive the value, the situation can't resolve, this is a deadlock.

You can make it not blocking by changing the channel creation to

c := make(chan int, 1) 

so that there's room for one item in the channel before it blocks.

But that's not what concurrency is about. Normally, you wouldn't use a channel without other goroutines to handle what you put inside. You could define a receiving goroutine like this :

func main() {
    c := make(chan int)    
    go func() {
        fmt.Println("received:", <-c)
    }()
    c <- 1   
}

Demonstration

Akavall
  • 82,592
  • 51
  • 207
  • 251
Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • 3
    I find it hard to think about, there is three situations : 1) unbuffered channel without a new goroutine -> deadlock , 2) buffered channel without a new goroutine -> no deadlock 3) unbuffered channel with a new goroutine -> run. – Salah Eddine Taouririt Sep 06 '13 at 15:08
  • 25
    When the channel is full, there must be a goroutine to take what the sender sends, or else the sender blocks until one comes. You can see an unbuffered channel as an always full channel. – Denys Séguret Sep 06 '13 at 15:21
  • 12
    Try to imagine the channel as a physical pipe : if it's too short, or is full, you must wait for somebody at the other end to take something or putting something inside would make some content fall on the ground. – Denys Séguret Sep 06 '13 at 15:37
  • 1
    Another helpful analogy for goroutines is to compare them with logic gates in a hardware circuit, the channels being the wires. In the example in the question, a 'gate' was incorrectly wired to itself in a way that it could send or receive, but not both at the same time. – Rick-777 Sep 07 '13 at 08:48
  • It's not the unbuffered channels are always full. On the contrary they are not full at all. Channels are like cables between goroutines. They don't have a buffer. Only the input and output points of channels may have buckets called buffers. – Inanc Gumus Nov 15 '19 at 14:52
19

In unbuffered channel writing to channel will not happen until there must be some receiver which is waiting to receive the data, which means in the below example

func main(){
    ch := make(chan int)
    ch <- 10   /* Main routine is Blocked, because there is no routine to receive the value   */
    <- ch
}

Now In case where we have other go routine, the same principle applies

func main(){
  ch :=make(chan int)
  go task(ch)
  ch <-10
}
func task(ch chan int){
   <- ch
}

This will work because task routine is waiting for the data to be consumed before writes happen to unbuffered channel.

To make it more clear, lets swap the order of second and third statements in main function.

func main(){
  ch := make(chan int)
  ch <- 10       /*Blocked: No routine is waiting for the data to be consumed from the channel */
  go task(ch)
}

This will leads to Deadlock

So in short, writes to unbuffered channel happens only when there is some routine waiting to read from channel, else the write operation is blocked forever and leads to deadlock.

NOTE: The same concept applies to buffered channel, but Sender is not blocked until the buffer is full, which means receiver is not necessarily to be synchronized with every write operation.

So if we have buffered channel of size 1, then your above mentioned code will work

func main(){
  ch := make(chan int, 1) /*channel of size 1 */
  ch <-10  /* Not blocked: can put the value in channel buffer */
  <- ch 
}

But if we write more values to above example, then deadlock will happen

func main(){
  ch := make(chan int, 1) /*channel Buffer size 1 */
  ch <- 10
  ch <- 20 /*Blocked: Because Buffer size is already full and no one is waiting to recieve the Data  from channel */
  <- ch
  <- ch
}
bharatj
  • 2,327
  • 1
  • 25
  • 25
  • 4
    **This is not true**: _"In unbuffered channel writing to channel will not happen until there must be some receiver which is waiting to receive the data"_. Writing to channel will happen and when a receiver shows up, it'll receive the data, if it doesn't, then deadlock or leak etc. – Inanc Gumus Jun 21 '18 at 20:19
  • @InancGumus, In the third code snippet above receiver shows up later but deadlocks: https://play.golang.org/p/HCP5KJ2aW_- – Hem Nov 14 '19 at 02:28
5

In this answer, I will try to explain the error message through which we can peek a little bit into how go works in terms of channels and goroutines

The first example is:

package main

import "fmt"

func main() {
    c := make(chan int)    
    c <- 1   
    fmt.Println(<-c)
}

The error message is:

fatal error: all goroutines are asleep - deadlock!

In the code, there are NO goroutines at all (BTW this error is in runtime, not compile time). When go runs this line c <- 1, it wants to make sure that the message in the channel will be received somewhere (i.e <-c). Go does NOT know if the channel will be received or not at this point. So go will wait for the running goroutines to finish until either one of the following happens:

  1. all of the goroutines are finished(asleep)
  2. one of the goroutine tries to receive the channel

In case #1, go will error out with the message above, since now go KNOWS that there is no way that a goroutine will receive the channel and it need one.

In case #2, the program will continue, since now go KNOWS that this channel is received. This explain the successful case in OP's example.

Chun Yang
  • 2,451
  • 23
  • 16
-2
  • Buffering removes synchronization.
  • Buffering makes them more like Erlang's mailboxes.
  • Buffered channels can be important for some problems but they are more subtle to reason about
  • By default channels are unbuffered, meaning that they will only accept sends
    (chan <-) if there is a corresponding receive (<- chan) ready to receive the sent value.
  • Buffered channels accept a limited number of values without a corresponding receiver for those values.

messages := make(chan string, 2) //-- channel of strings buffering up to 2 values.

Basic sends and receives on channels are blocking. However, we can use select with a default clause to implement non-blocking sends, receives, and even non-blocking multi-way selects.

Awesome Infinity
  • 121
  • 1
  • 2
  • 6