4

I would like to know if the go language allows checking for multiple channels being ready at the same time.

Here is a somewhat contrived example of what I'm trying to do. (The actual reason is to see if I can implement petrinets natively in go)

package main

import "fmt"

func mynet(a, b, c, d <-chan int, res chan<- int) {
    for {
        select {
        case v1, v2 := <-a, <-b:
            res <- v1+v2
        case v1, v2 := <-c, <-d:
            res <- v1-v2
        }
    }
}

func main() {
        a := make(chan int)
        b := make(chan int)
        c := make(chan int)
        d := make(chan int)
        res := make(chan int, 10)
        go mynet(a, b, c, d, res)

        a <- 5
        c <- 5
        d <- 7
        b <- 7
        fmt.Println(<-res)
        fmt.Println(<-res)
}

This doesn't compile as shown. It can be made to compile by only checking one channel, but then it can trivially deadlock if that channel is ready but the other one is not.

package main

import "fmt"

func mynet(a, b, c, d <-chan int, res chan<- int) {
    for {
        select {
        case v1 := <-a:
            v2 := <-b
            res <- v1+v2
        case v1 := <-c:
            v2 := <-d
            res <- v1-v2
        }
    }
}

func main() {
        a := make(chan int)
        b := make(chan int)
        c := make(chan int)
        d := make(chan int)
        res := make(chan int, 10)
        go mynet(a, b, c, d, res)

        a <- 5
        c <- 5
        d <- 7
        //a <- 5
        b <- 7
        fmt.Println(<-res)
        fmt.Println(<-res)
}

In the general case, I might have multiple cases waiting on the same channel, e.g.

case v1, v2 := <-a, <-b:
...
case v1, v2 := <-a, <-c:
...

so I can't commit to either branch when a value is ready on channel a: only when all values are ready.

  • 1
    Nope. You can do a non-blocking receive (using a `select` with one receive case and a `default`) on a channel to not block if `b` is not ready (but then you've already taken a value from `a`), and you can do `len()` on a buffered channel to speculatively check how many items are in the buffer (though that can change before your receive executes). – twotwotwo Jul 29 '15 at 17:54

2 Answers2

2

You can't select on multiple channels simultaneously. What you can do is implement a fan-in pattern for coalescing your values from multiple channels.

A rough example based on your code might look like:

func collect(ret chan []int, chans ...<-chan int) {
    ints := make([]int, len(chans))
    for i, c := range chans {
        ints[i] = <-c
    }
    ret <- ints
}

func mynet(a, b, c, d <-chan int, res chan<- int) {
    set1 := make(chan []int)
    set2 := make(chan []int)
    go collect(set1, a, b)
    go collect(set2, c, d)
    for {
        select {
        case vs := <-set1:
            res <- vs[0] + vs[1]
        case vs := <-set2:
            res <- vs[0] + vs[1]
        }
    }
}
JimB
  • 104,193
  • 13
  • 262
  • 255
  • I guess my example wasn't particularly clear. If I was just reading from (a,b) and (c,d) then the simple solution is to have two separate goroutines. But suppose there are three channels (a,b,c), and one branch reads from (a,b) and the other branch reads from (a,c) ? – Brian Candler Jul 29 '15 at 19:13
  • 3
    @BrianCandler: I skipped over the comment about Petri nets. This isn't something you can do with channels alone, and I have a feeling that CSP isn't going to be useful in representing this (though the inverse is possible). In order to determine if multiple channels can succeed and then operate on them, you need to stop all other concurrent processes doing the same. Goroutines and channels don't seem to be a good fit for the problem space. – JimB Jul 29 '15 at 19:38
  • 1
    Thank you @JimB, that's the answer I was looking for. – Brian Candler Jul 30 '15 at 08:00
0

Individual channels for the two different pairs that you are accumulating may work:

package main

import "fmt"

func mynetPlus(a, b <-chan int, res chan<- int) {
    for {
        select {
        case v1 := <-a:
            v2 := <-b
            res <- v1 + v2
        case v2 := <-b:
            v1 := <-a
            res <- v1 + v2
        }
    }
}

func mynetMinus(c, d <-chan int, res chan<- int) {
    for {
        select {
        case v1 := <-c:
            v2 := <-d
            res <- v1 + v2
        case v2 := <-d:
            v1 := <-c
            res <- v1 + v2
        }
    }
}

func main() {
    a := make(chan int)
    b := make(chan int)
    c := make(chan int)
    d := make(chan int)
    res := make(chan int, 10)
    go mynetPlus(a, b, res)
    go mynetMinus(c, d, res)

    a <- 5
    c <- 5
    d <- 7
    //a <- 5
    b <- 7
    fmt.Println(<-res)
    fmt.Println(<-res)
}
LanceH
  • 1,726
  • 13
  • 20