开始执行两个goroutine的无限循环,我可以使用下面的代码:
收到消息后,它将启动一个新的goroutine并继续下去。
c1 := make(chan string)
c2 := make(chan string)
go DoStuff(c1, 5)
go DoStuff(c2, 2)
for ; true; {
select {
case msg1 := <-c1:
fmt.Println("received ", msg1)
go DoStuff(c1, 1)
case msg2 := <-c2:
fmt.Println("received ", msg2)
go DoStuff(c2, 9)
}
}
我现在想对N goroutines有相同的行为,但是在这种情况下select语句会怎么样?
这是我开始使用的代码位,但我很困惑如何编写select语句
numChans := 2
//I keep the channels in this slice, and want to "loop" over them in the select statemnt
var chans = [] chan string{}
for i:=0;i<numChans;i++{
tmp := make(chan string);
chans = append(chans, tmp);
go DoStuff(tmp, i + 1)
//How shall the select statment be coded for this case?
for ; true; {
select {
case msg1 := <-c1:
fmt.Println("received ", msg1)
go DoStuff(c1, 1)
case msg2 := <-c2:
fmt.Println("received ", msg2)
go DoStuff(c2, 9)
}
}
答案 0 :(得分:131)
func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)
Select执行由案例列表描述的选择操作。喜欢 Go select语句,它会阻塞,直到至少有一个案例可以 继续,做出统一的伪随机选择,然后执行它 案件。它返回所选案例的索引,如果是这种情况,则返回 接收操作,接收的值和指示是否的布尔值 该值对应于通道上的发送(而不是零) 由于频道已关闭而收到的值。)
传入一个SelectCase
结构数组,用于标识要选择的通道,操作方向以及发送操作时要发送的值。
所以你可以这样做:
cases := make([]reflect.SelectCase, len(chans))
for i, ch := range chans {
cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch)}
}
chosen, value, ok := reflect.Select(cases)
# ok will be true if the channel has not been closed.
ch := chans[chosen]
msg := value.String()
您可以在此处尝试更加充实的示例:http://play.golang.org/p/8zwvSk4kjx
答案 1 :(得分:68)
您可以通过将每个频道包装在goroutine中来实现此目的,该goroutine将“转发”消息“转发”到共享的“聚合”频道。例如:
agg := make(chan string)
for _, ch := range chans {
go func(c chan string) {
for msg := range c {
agg <- msg
}
}(ch)
}
select {
case msg <- agg:
fmt.Println("received ", msg)
}
如果您需要知道消息来自哪个频道,您可以将其包含在包含任何额外信息的结构中,然后再将其转发到聚合频道。
在我的(有限)测试中,这种方法大大超出了使用反射包:
$ go test dynamic_select_test.go -test.bench=.
...
BenchmarkReflectSelect 1 5265109013 ns/op
BenchmarkGoSelect 20 81911344 ns/op
ok command-line-arguments 9.463s
基准代码here
答案 2 :(得分:18)
为了扩展对先前答案的一些评论并提供更清晰的比较,这里给出了相同的输入,一段要读取的通道以及调用每个值的函数的两种方法的示例。知道价值来自哪个渠道。
这些方法之间存在三个主要差异:
复杂性。虽然它可能部分是读者偏好,但我发现渠道方法更具惯用性,直接性和可读性。
性能。在我的Xeon amd64系统上,goroutines + channel out执行反射解决方案大约两个数量级(一般来说Go中的反射通常较慢,只应在绝对需要时使用)。当然,如果处理结果的函数或输入通道的值写入有任何明显的延迟,这种性能差异很容易变得无关紧要。
阻止/缓冲语义。这一点的重要性取决于用例。大多数情况下它要么无关紧要,要么goroutine合并解决方案中的轻微额外缓冲可能有助于吞吐量。但是,如果希望在没有阻塞任何其他编写器的情况下,只有单个编写器被解除阻塞且在之前完全处理它的值,那么这只能通过反射解决方案实现。
请注意,如果不需要发送通道的“id”或者源通道永远不会关闭,则可以简化这两种方法。
Goroutine合并频道:
// Process1 calls `fn` for each value received from any of the `chans`
// channels. The arguments to `fn` are the index of the channel the
// value came from and the string value. Process1 returns once all the
// channels are closed.
func Process1(chans []<-chan string, fn func(int, string)) {
// Setup
type item struct {
int // index of which channel this came from
string // the actual string item
}
merged := make(chan item)
var wg sync.WaitGroup
wg.Add(len(chans))
for i, c := range chans {
go func(i int, c <-chan string) {
// Reads and buffers a single item from `c` before
// we even know if we can write to `merged`.
//
// Go doesn't provide a way to do something like:
// merged <- (<-c)
// atomically, where we delay the read from `c`
// until we can write to `merged`. The read from
// `c` will always happen first (blocking as
// required) and then we block on `merged` (with
// either the above or the below syntax making
// no difference).
for s := range c {
merged <- item{i, s}
}
// If/when this input channel is closed we just stop
// writing to the merged channel and via the WaitGroup
// let it be known there is one fewer channel active.
wg.Done()
}(i, c)
}
// One extra goroutine to watch for all the merging goroutines to
// be finished and then close the merged channel.
go func() {
wg.Wait()
close(merged)
}()
// "select-like" loop
for i := range merged {
// Process each value
fn(i.int, i.string)
}
}
反射选择:
// Process2 is identical to Process1 except that it uses the reflect
// package to select and read from the input channels which guarantees
// there is only one value "in-flight" (i.e. when `fn` is called only
// a single send on a single channel will have succeeded, the rest will
// be blocked). It is approximately two orders of magnitude slower than
// Process1 (which is still insignificant if their is a significant
// delay between incoming values or if `fn` runs for a significant
// time).
func Process2(chans []<-chan string, fn func(int, string)) {
// Setup
cases := make([]reflect.SelectCase, len(chans))
// `ids` maps the index within cases to the original `chans` index.
ids := make([]int, len(chans))
for i, c := range chans {
cases[i] = reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(c),
}
ids[i] = i
}
// Select loop
for len(cases) > 0 {
// A difference here from the merging goroutines is
// that `v` is the only value "in-flight" that any of
// the workers have sent. All other workers are blocked
// trying to send the single value they have calculated
// where-as the goroutine version reads/buffers a single
// extra value from each worker.
i, v, ok := reflect.Select(cases)
if !ok {
// Channel cases[i] has been closed, remove it
// from our slice of cases and update our ids
// mapping as well.
cases = append(cases[:i], cases[i+1:]...)
ids = append(ids[:i], ids[i+1:]...)
continue
}
// Process each value
fn(ids[i], v.String())
}
}
[完整代码on the Go playground。]
答案 3 :(得分:2)
为什么假设有人发送事件,这种方法不起作用?
func main() {
numChans := 2
var chans = []chan string{}
for i := 0; i < numChans; i++ {
tmp := make(chan string)
chans = append(chans, tmp)
}
for true {
for i, c := range chans {
select {
case x = <-c:
fmt.Printf("received %d \n", i)
go DoShit(x, i)
default: continue
}
}
}
}
答案 4 :(得分:1)
可能更简单的选择:
为什么不只使用一个通道数组,为什么不将一个通道作为参数传递给在单独的goroutine上运行的函数,然后在使用者goroutine中监听该通道?
这允许您仅在侦听器中的一个通道上进行选择,从而进行简单选择,并避免创建新的goroutine来汇总来自多个通道的消息?
答案 5 :(得分:1)
我们实际上对此主题进行了一些研究,并找到了最佳解决方案。我们使用了一段时间 StatusListener listener = new StatusListener() {
@Override
public void onException(Exception e) {
e.printStackTrace();
}
@Override
public void onDeletionNotice(StatusDeletionNotice arg) {
}
@Override
public void onScrubGeo(long userId, long upToStatusId) {
}
@Override
public void onStallWarning(StallWarning warning) {
}
@Override
public void onStatus(Status status) {
System.out.println("tweeted" + status.getText());
}
@Override
public void onTrackLimitationNotice(int numberOfLimitedStatuses) {
}
};
//twitter.users();
TwitterStream twitterStream = new TwitterStreamFactory(config).getInstance();
twitterStream.addListener(listener);
FilterQuery query = new FilterQuery();
query.follow(userId);
twitterStream.filter(query);
twitterStream.sample();
,它是解决问题的好方法。它比每个通道的 goroutine 轻得多,而且操作简单。但不幸的是,它并没有真正支持大量的频道,这就是我们的情况,所以我们发现了一些有趣的东西并写了一篇关于它的博客文章:https://cyolo.io/blog/how-we-enabled-dynamic-channel-selection-at-scale-in-go/
我将总结一下那里写的内容: 我们为最多 32 的指数的 2 次幂的每个结果静态创建了一批 select..case 语句,以及一个路由到不同案例并通过聚合通道聚合结果的函数。
此类批次的示例:
reflect.Select
以及使用这些类型的 func select4(ctx context.Context, chanz []chan interface{}, res chan *r, r *r, i int) {
select {
case r.v, r.ok = <-chanz[0]:
r.i = i + 0
res <- r
case r.v, r.ok = <-chanz[1]:
r.i = i + 1
res <- r
case r.v, r.ok = <-chanz[2]:
r.i = i + 2
res <- r
case r.v, r.ok = <-chanz[3]:
r.i = i + 3
res <- r
case <-ctx.Done():
break
}
}
批次聚合来自任意数量通道的第一个结果的逻辑:
select..case