是否可以将多个通道复用为一个?

时间:2012-06-11 11:55:19

标签: concurrency go channel

这个想法是在一个切片中有一个可变数量的通道,将通过它们接收的每个值推入一个通道,并在最后一个输入通道关闭后关闭该输出通道。这样的事情,但对于多个渠道超过两个:

func multiplex(cin1, cin2, cout chan int) {
    n := 2
    for {
        select {
        case v, ok := <-cin1:
            if ok {
                cout <- v
            } else {
                n -= 1
            }

        case v, ok := <-cin2:
            if ok {
                cout <- v
            } else {
                n -= 1
            }
        }

        if n == 0 {
            close(cout)
            break
        }
    }
}

上面的代码避免了繁忙的循环,因为没有default的情况,这很好(编辑:它看起来像“,ok”的存在使得select语句无阻塞,并且循环繁忙但是为了这个例子,可以把代码想象成阻止它。使用任意数量的输入通道也可以实现相同的功能吗?显然,这可以通过将切片成对地减少到单个通道来完成,但如果可能的话,我会对更简单的解决方案更感兴趣。

3 个答案:

答案 0 :(得分:26)

我相信这段代码可以满足您的需求。我已经更改了签名,因此很明显输入和输出只能用于一个方向的通信。请注意添加sync.WaitGroup,您需要某种方式让所有输入信号表明它们已经完成,这非常简单。

func combine(inputs []<-chan int, output chan<- int) {
  var group sync.WaitGroup
  for i := range inputs {
    group.Add(1)
    go func(input <-chan int) {
      for val := range input {
        output <- val
      }
      group.Done()
    } (inputs[i])
  }
  go func() {
    group.Wait()
    close(output)
  } ()
}

答案 1 :(得分:2)

编辑:添加成对缩减示例代码并重新排序部分答案。

首选解决方案是“不进行重组,以便您没有一条频道”。重组通常可以利用多个goroutine可以发送到单个通道的功能。因此,不要让每个源都在不同的通道上发送,然后必须处理从一堆通道接收,只需创建一个通道,让所有源都在该通道上发送。

Go不提供从一片频道接收的功能。这是一个经常被问到的问题,虽然刚刚给出的解决方案是首选,但有一些方法可以对其进行编程。我认为你在原始问题中提出的一个解决方案是说“减少成对切片”是一个二元分而治之的解决方案。只要您有将两个通道复用为一个的解决方案,这种方法就可以正常工作。您的示例代码非常接近工作。

你只是缺少一个让你的示例代码工作的小技巧。在递减n的位置,添加一行以将通道变量设置为nil。例如,我把代码读成了

    case v, ok := <-cin1:
        if ok {
            cout <- v
        } else {
            n--
            cin1 = nil
        }
    case v, ok := <-cin2:
        if ok {
            cout <- v
        } else {
            n--
            cin2 = nil
        }
    }

此解决方案可以满足您的需求并且不会在等待。

那么,这个完整的例子将这个解决方案整合到一个多路复用切片的函数中:

package main

import (
    "fmt"
    "time"
)

func multiplex(cin []chan int, cout chan int) {
    var cin0, cin1 chan int
    switch len(cin) {
    case 2:
        cin1 = cin[1]
        fallthrough
    case 1:
        cin0 = cin[0]
    case 0:
    default:
        cin0 = make(chan int)
        cin1 = make(chan int)
        half := len(cin) / 2
        go multiplex(cin[:half], cin0)
        go multiplex(cin[half:], cin1)
    }
    for cin0 != nil || cin1 != nil {
        select {
        case v, ok := <-cin0:
            if ok {
                cout <- v
            } else {
                cin0 = nil
            }
        case v, ok := <-cin1:
            if ok {
                cout <- v
            } else {
                cin1 = nil
            }
        }
    }
    close(cout)
}

func main() {
    cin := []chan int{
        make(chan int),
        make(chan int),
        make(chan int),
    }
    cout := make(chan int)
    for i, c := range cin {
        go func(x int, cx chan int) {
            for i := 1; i <= 3; i++ {
                time.Sleep(100 * time.Millisecond)
                cx <- x*10 + i
            }
            close(cx)
        }(i, c)
    }
    go multiplex(cin, cout)
    for {
        select {
        case v, ok := <-cout:
            if ok {
                fmt.Println("main gets", v)
            } else {
                return
            }
        }
    }
}

答案 2 :(得分:0)

使用goroutines我制作了这个。这是你想要的吗?

package main

import (
    "fmt"
)

func multiplex(cin []chan int, cout chan int) {
    n := len(cin)
    for _, ch := range cin {
        go func(src chan int) {
            for {
                v, ok := <-src
                if ok {
                    cout <- v
                } else {
                    n-- // a little dangerous. Maybe use a channel to avoid missed decrements
                    if n == 0 {
                        close(cout)
                    }
                    break
                }
            }
        }(ch)
    }
}

// a main to test the multiplex
func main() {
    cin := make([]chan int, 3)
    cin[0] = make(chan int, 2)
    cin[1] = make(chan int, 2)
    cin[2] = make(chan int, 2)
    cout := make(chan int, 2)
    multiplex(cin, cout)
    cin[1] <- 1
    cin[0] <- 2
    cin[2] <- 3
    cin[1] <- 4
    cin[0] <- 5
    close(cin[1])
    close(cin[0])
    close(cin[2])
    for {
        v, ok := <-cout
        if ok {
            fmt.Println(v)
        } else {
            break
        }
    }
}

编辑:参考文献:

http://golang.org/ref/spec#Receive_operator

http://golang.org/ref/spec#Close