Home > Net >  Why is the order of channels receiving causing/resolving a deadlock in Golang?
Why is the order of channels receiving causing/resolving a deadlock in Golang?

Time:02-23

I've boiled my issue down to this simple example below. I am invoking a goroutine that takes two channels and sends one message to each. Then I am attempting to receive those messages further along. However, the order of channels receiving matters. If I use the same order I sent the messages, the program runs. If I switch, it does not.

I would have expected the goroutine to run independently from retrieving the messages, allowing me to receive from whichever channel I wanted to first.

I can solve this by sending messages to a single channel per goroutine (2 goroutines).

Could someone explain why there is an order dependence here and why 2 separate goroutines resolves that dependence?

package main

import "fmt"

func main() {
    chanA := make(chan string)
    chanB := make(chan string)

    go func() {
        chanA <- "el"
        chanB <- "el"
    }()

    // if B is received before A, fatal error
    // if A is received before B, completes
    <-chanB
    <-chanA

    fmt.Println("complete")
}

CodePudding user response:

You will need to buffer your channels. A buffered channel can store so many elements before it will block.

chanA := make(chan string, 1)
chanA <- "el" // This will not block
fmt.Println("Hello World")

When you do chanA <- "el" on the buffered channel above, the element gets placed into the buffer and the thread does not block. If you add a second element, it will then block as there is no room in the buffer:

chanA := make(chan string, 1)
chanA <- "el"
chanA <- "el" // <- This will block, as the buffer is full

In your example, you have a buffer of 0. So the first write to the channel is blocked, and requires another thread to read the value to unblock.

https://go.dev/play/p/6GbsVW4d0Mg

    chanA := make(chan string)
    go func() {
        time.Sleep(time.Second)
        fmt.Println("Pop:", <-chanA) // Unblock the writer
    }()
    chanA <- "el"

Extra knowledge

If you do not want a thread to block, you can wrap a channel insert in a select. This will ensure if the channel is full, your application does not deadlock. One cheap way of fixing this is a larger buffer...

https://go.dev/play/p/kKR-lrCO4FX

    select {
    case chanA <- "el":
    default:
        return fmt.Errorf("value not written: %s", value)
    }

CodePudding user response:

A write to or read from an unbuffered channel will block until there is a goroutine to write to or read from that channel. When the goroutine writes to a, it will block until the main goroutine can read from a, but main goroutine is also blocked waiting to read from b, hence deadlock.

CodePudding user response:

this is how goroutine works:

a goroutine will be blocked read/write on a channel unless if find another goroutine which write/read from the same channel.

Pay attention to read/write and write/read in the above blocked quote.

In your case, your anon goroutine(which you kicked off with go) waits to write on channelA until it finds a goroutine which reads from channelA. The main goroutine waits to read from channelB unless it finds a goroutine that reads from it.

You can think it this way, any line written after read/write to channel won't be considered unless go finds another routine which write/read from the same channel.

So, if you change either read or write order you will not have deadlock or as you said another goroutine will do the job too.

Hope it's clear.

  • Related