Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the use of an unbuffered channel in the same goroutine result in a deadlock?

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.

like image 977
Salah Eddine Taouririt Avatar asked Sep 06 '13 14:09

Salah Eddine Taouririt


People also ask

When to use buffered vs unbuffered channel?

An unbuffered channel is used to perform synchronous communication between goroutines while a buffered channel is used for perform asynchronous communication. An unbuffered channel provides a guarantee that an exchange between two goroutines is performed at the instant the send and receive take place.

What is Goroutine and channel?

Goroutines and Channels are a lightweight built-in feature for managing concurrency and communication between several functions executing at the same time.

How do I close a channel in Golang?

We can close a channel in Golang with the help of the close() function. Once a channel is closed, we can't send data to it, though we can still read data from it. A closed channel denotes a case where we want to show that the work has been done on this channel, and there's no need for it to be open.

What are go routines?

A goroutine is a function that executes simultaneously with other goroutines in a program and are lightweight threads managed by Go. A goroutine takes about 2kB of stack space to initialize.


2 Answers

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

like image 52
Denys Séguret Avatar answered Oct 12 '22 12:10

Denys Séguret


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 } 
like image 37
bharatj Avatar answered Oct 12 '22 12:10

bharatj