goconcurrencydeadlockchannelgoroutine

FanIn pattern blocks when trying to send and receive simultaneously


package main

import (
    "fmt"
)

func main() {
    even := make(chan int)
    odd := make(chan int)
    quit := make(chan int)
    fanin := make(chan int)

    go send(even, odd, quit)

    go receive(even, odd, quit, fanin)

    for v := range fanin {
        fmt.Println(v)
    }

    fmt.Println("about to exit")
}

// send channel
func send(even, odd, quit chan<- int) {
    for i := 0; i < 100; i++ {
        if i%2 == 0 {
            even <- i
        } else {
            odd <- i
        }
    }
    close(quit)
}

// receive channel
func receive(even, odd, quit <-chan int, fanin chan<- int) {
thisLoop:
    for {
        select {
        case fanin <- <-even: //problem is here
        case fanin <- <-odd:
        case <-quit:
            break thisLoop
        }
    }
    close(fanin)
}

This was an example of bad approach in fanin pattern for concurrency in Go. There happens deadlocks when ranging fanin in the end of yielding all the values and in first case of select:

...
95
96
99
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
        /home/floosde/go/src/floosde/main.go:17 +0x194

goroutine 19 [chan receive]:
main.receive(...)
        /home/floosde/go/src/floosde/main.go:41
created by main.main in goroutine 1
        /home/floosde/go/src/floosde/main.go:15 +0x137
exit status 2

But when I made an assignment first instead of simultaneous sending, It started to work without deadlock:

// receive channel
func receive(even, odd, quit <-chan int, fanin chan<- int) {
    thisLoop:
    for {
        select {
        case v := <-even: // Fixed version
            fanin <- v 
        case v := <-odd: // Fixed version
            fanin <- v
        case <-quit:
            break thisLoop
        }
    }
    close(fanin)
}

Just can't understand the reasons of this behaviour.


Solution

  • A select statement evaluates the arguments of a send or receive operation. When you write

    case fanin <- <-even:
    

    it first evaluates <-even, and then checks if fanin is ready to receive. If even is not ready, this will block.

    In short, a select statement selects on one channel operation for each case. You are trying to do two.