2012-06-11 4 views
11

アイデアは、スライス内に可変数のチャンネルを持ち、それらを介して受け取った各値を1つのチャンネルにプッシュし、入力チャンネルの最後のチャンネルが閉じられるとこの出力チャンネルを閉じることです。このような何かが、2以上のチャンネル数について:複数のチャネルを1つに多重化することはできますか?

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

良い(EDITあるなしdefault場合は、存在しないため、上記のコードがビジーループを避ける:それは「OK」の存在のように見えますselect文を非ブロックにしてしまい、結局ループがビジーですが、例のためにコードをブロックするかのように考えてください)。任意の数の入力チャンネルで同じ種類の機能を実現できますか?明らかに、スライスを1つのチャンネルにペアで縮小することでこれを行うことができますが、可能であれば、より簡単なソリューションにもっと興味があります。

答えて

24

。私はシグネチャを変更して、入力と出力が一方向の通信にのみ使用されるべきであることが明らかであるようにしました。 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

ああ、非常に良い解決策、明確かつ簡潔です。ありがとうございました! – elpres

+0

複数のゴルーチンの代わりにリフレクションを使用して問題を解決する関数(https://godoc.org/github.com/eapache/channels#Multiplex)を持つパッケージが用意されました。 – Evan

0

私はこれを作成しました。それはあなたが望むものですか?

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

EDIT:参照:

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

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

+0

「OK」でチャンネルから値を読み取った場合、操作はブロックされないと言われています。 'ok'の値は単に' false'であり、実行は続行されます。これが正しければ(私は新しくなっています)、チャネルが空ではあるがまだ閉じていなければ、 'if ok'行は' false'として評価され、 'else'分岐を実行します。 "v、ok:= < - src"、そして "if"をselect文で置き換えると、動作するかもしれません。それをテストしました。あなたの返信をありがとう、btw。 – elpres

+1

操作がブロックされていないことを、あなたはどこでお知りになりましたか?私はそれを見つけることはできませんし、それは私が観察するものと一致するように見えません。それがブロックされていないことを医者から聞いた*チャンネルが閉じられた*。 –

+1

これは古いバージョンの仕様から来ているようです。 [ここ](http://go.googlecode.com/hg/doc/go_spec.html?r=c64e293#Communication_operators)では、「メソッド式」の前の最後の段落を見てください。現在のバージョンでは、この節がちょっと変わって、 "チャンネルが閉じられていてempty_(false)"なのでゼロの値が返されていると言います。これは、 'false'のように聞こえるのは、チャンネルが排水され閉鎖された後にのみ返されます。それは私が間違っていることを意味します。 – elpres

2

編集:ペアワイズ還元サンプルコードと回答の並べ替え部を添加します。

「解決策の1つではなく、チャンネルのスライスを持たないようにする」の答えが好ましいです。再構成では、複数のゴルーチンが1つのチャネルに送信できる機能を利用することがよくあります。だから、あなたのソースのそれぞれを別々のチャンネルで送信してから、たくさんのチャンネルから受信することに対処する代わりに、ただ1つのチャンネルを作成し、すべてのソースがそのチャンネルを送信するようにしてください。

Goには、チャンネルの一部から受信する機能はありません。それは頻繁に質問され、ちょうど与えられた解決が好まれる間、それをプログラムする方法があります。あなたが元の質問で "スライスペアを減らす"と言って提案していた解決策は、バイナリの分割と征服の解決策です。 2つのチャンネルを1つに多重化するソリューションがあれば、これはうまくいきます。これのためのあなたのコード例は非常に作業に近いです。

サンプルコードを動作させるためのちょっとした工夫がありません。 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 
      } 
     } 
    } 
} 
+1

いいえ、あまりありません。私が署名 'func multiplex(cin [] chan int、cout chan int)で関数として探しているのは、2つにハードコードされているのではなく、任意の数の入力チャンネルで操作できるものです。 – elpres

関連する問題