如何在Go中选择动态通道列表上的输入?

huangapple go评论89阅读模式
英文:

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

问题

Go有一种机制可以从多个通道中进行阻塞读取,即select语句。因此,你可以这样说:

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

将会阻塞,直到从这两个通道中的任意一个接收到输入。非常好。

但是,这要求我在源代码中指定我要轮询的通道数量。如果我有一个通道的切片或数组,并且我希望在任何一个通道上接收到输入之前进行阻塞,该怎么办?

英文:

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?

答案1

得分: 8

自从go1.1版本以来,有一个适当的API可以动态地进行选择集操作。

下面是一个完整且可用的示例:

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)
    }
}

您可以在playground上进行交互式操作。

英文:

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

Here's a complete and usable example:

package main

import (
	&quot;log&quot;
	&quot;reflect&quot;
)

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 &lt; 5; i++ {
		channels = append(channels, make(chan int))
	}

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

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

You can play around with it interactively on the playground

答案2

得分: 5

只是一个想法,但你可以使用多路复用模式,其中你可以生成一个带有2个通道的goroutine,它会在这两个通道上阻塞并将输出发送到一个新的通道。然后你可以动态地从列表中构建这些通道的树状结构,将所有内容汇聚到一个单一的通道中,然后在该通道上进行读取。

英文:

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.

答案3

得分: 4

包主要

导入 "fmt"

功能 主要() {
c1 := make(chan int)
c2 := make(chan int)

去 功能() { c1 <- 1 }()
去 功能() { c2 <- 2 }()

cs := []chan int{c1, c2}
cm := make(chan [2]int)

对于 idx, c := 范围(cs) {
    去 功能(idx int, c chan int) {
        cm <- [2]int{idx, <-c}
    }(idx, c)
}

fmt.Print(<-cm)
fmt.Print(<-cm)

}

打印 [0 1][1 2] (或者可能是 [1 2][0 1])。

英文:
package main

import &quot;fmt&quot;

func main() {
    c1 := make(chan int)
    c2 := make(chan int)

    go func() { c1 &lt;- 1 }()
    go func() { c2 &lt;- 2 }()

    cs := []chan int{c1, c2}
    cm := make(chan [2]int)

    for idx, c := range(cs) {
        go func(idx int, c chan int) {
            cm &lt;- [2]int{idx, &lt;-c}
        }(idx, c)
    }

    fmt.Print(&lt;-cm)
    fmt.Print(&lt;-cm)
}

prints [0 1][1 2] (or maybe [1 2][0 1]).

答案4

得分: 1

也许可以这样实现?

// multiplex函数接受一个chan int的切片,并返回一个chan int,用于在所有通道之间进行复用。
func multiplex(chs []<-chan int) <-chan int {
    c := make(chan int)
    d := make(chan bool)
    for _, ch := range chs {
        go func(ch <-chan int) {
            for r := range ch {
                c <- r
            }
            d <- true
        }(ch)
    }
    go func() {
        for i := 0; i < len(chs); i++ {
            <-d
        }
        close(c)
    }()
    return c
}
英文:

Perhaps something like this may apply?

// multiplex takes a slice of chan ints and returns a channel
// that multiplexes between all of them.
func multiplex(chs []&lt;-chan int) &lt;-chan int {
	c := make(chan int)
	d := make(chan bool)
	for _, ch := range chs {
		go func(ch &lt;-chan int) {
			for r := range ch {
				c &lt;- r
			}
			d &lt;- true
		}(ch)
	}
	go func() {
		for i := 0; i &lt; len(chs); i++ {
			&lt;-d
		}
		close(c)
	}()
	return c
}

huangapple
  • 本文由 发表于 2010年11月19日 07:30:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/4220745.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定