Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unbuffered channel in Go

Tags:

go

Here is a simple example code about unbuffered channels:

ch01 := make(chan string)

go func() {
    fmt.Println("We are in the sub goroutine")
    fmt.Println(<-ch01)
}()

fmt.Println("We are in the main goroutine")
ch01 <- "Hello"

The result I got:

We are in the main goroutine 
We are in the sub goroutine 
Hello

Go playground: https://play.golang.org/p/rFWQbwXRzGw

From my understanding, the send operation blocked the main goroutine, until the sub goroutine executed a receive operation on channel ch01. Then the program exited.

After placing the sub goroutine after the send operation like that:

fmt.Println("We are in the main goroutine")
ch01 <- "Hello"

go func() {
    fmt.Println("We are in the sub goroutine")
    fmt.Println(<-ch01)
}()

A deadlock occurred:

We are in the main goroutine
fatal error: all goroutines are asleep - deadlock!

go playground https://play.golang.org/p/DmRUiBG4UmZ

What happened this time? Did that mean after ch01 <- "Hello" the main goroutine was immediately blocked so that the sub goroutine had no chance to run? If it is true, how should I understand the result of the first code example?(At first in main goroutine, then in sub goroutine).

like image 794
Pauli Avatar asked Jan 29 '23 06:01

Pauli


2 Answers

An unbuffered channel blocks on send until a receiver is ready to read. In your first example a reader is set up first, so when the send occurs it can be sent immediately.

In your second example, the send happens before a receiver is ready so the send blocks and the program deadlocks.

You could fix the second example by making a buffered channel, but there is a chance you won't ever see the output from the goroutine as the program may exit (the main goroutine) before the output buffer is flushed. The goroutine may not even run as main exits before it can be scheduled.

like image 123
sberry Avatar answered Jan 30 '23 18:01

sberry


First of all, go-routines run concurrently. In 1st example, the sub-goroutine has already started, but in 2nd example, the go-routine hasn't started yet when the send operation appears.

Think about line by line.

In 1st example, the sub-goroutine has started concurrently before the send operation appears on the main go-routine. As a result, when the the send operation happens, there is already an receiver (sub-goroutine) exists.

If you tweak the 1st example,

package main

import (
    "fmt"
    "time"
)

func main() {
    ch01 := make(chan string)

    go func() {
        fmt.Println("We are in the sub goroutine")
        fmt.Println(<-ch01)
    }()

    // wait for start of sub-routine
    time.Sleep(time.Second * 2)

    fmt.Println("We are in the main goroutine")
    ch01 <- "Hello"

    // wait for the routine to receive and print the string
    time.Sleep(time.Second * 2)

}

The output will be

We are in the sub goroutine
We are in the main goroutine
Hello

So, you can see that the sub-goroutine has already started.and it is waiting to receive on channel. When the main goroutine send string in channel, the sub-goroutine resumes and receives the signal.

But in 2nd example, The program has stuck in main go routine send operation, and the sub go routine has not started yet and will not start, because the program has not got that line yet. so there is no other receiver to receive the signal. So the program stuck in deadlock.

like image 34
Abdullah Al Maruf - Tuhin Avatar answered Jan 30 '23 20:01

Abdullah Al Maruf - Tuhin