Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to select for input on a dynamic list of channels in Go?

Tags:

go

channel

Go has a mechanism to do a blocking read from one of several channels, the select statement. So you can say

select {
    case <- c1:
    case <- c2:
}

will block until we get input from either of these two channels. Very nice.

But this requires that I specify in the source code how many channels I want to poll. What if I have a slice or array of channels and I want to block until I get input on any of them?

like image 441
poolie Avatar asked Nov 18 '10 23:11

poolie


2 Answers

Just a thought, but you could use a multiplexing pattern, where you spawn off a goroutine with 2 channels that blocks on both and sends the output to a new channel. Then you can just build up a tree of these dynamically from your list that funnels everything down to a single channel, which you then read on.

like image 135
Lily Ballard Avatar answered Sep 25 '22 02:09

Lily Ballard


Since go1.1, there's a proper API to dynamically do select sets.

Here's a complete and usable example:

package main

import (
    "log"
    "reflect"
)

func sendToAny(ob int, chs []chan int) int {
    set := []reflect.SelectCase{}
    for _, ch := range chs {
        set = append(set, reflect.SelectCase{
            Dir:  reflect.SelectSend,
            Chan: reflect.ValueOf(ch),
            Send: reflect.ValueOf(ob),
        })
    }
    to, _, _ := reflect.Select(set)
    return to
}

func recvFromAny(chs []chan int) (val int, from int) {
    set := []reflect.SelectCase{}
    for _, ch := range chs {
        set = append(set, reflect.SelectCase{
            Dir:  reflect.SelectRecv,
            Chan: reflect.ValueOf(ch),
        })
    }
    from, valValue, _ := reflect.Select(set)
    val = valValue.Interface().(int)
    return
}

func main() {
    channels := []chan int{}
    for i := 0; i < 5; i++ {
        channels = append(channels, make(chan int))
    }

    go func() {
        for i := 0; i < 10; i++ {
            x := sendToAny(i, channels)
            log.Printf("Sent %v to ch%v", i, x)
        }
    }()

    for i := 0; i < 10; i++ {
        v, x := recvFromAny(channels)
        log.Printf("Received %v from ch%v", v, x)
    }
}

You can play around with it interactively on the playground

like image 41
Dustin Avatar answered Sep 26 '22 02:09

Dustin