2012-06-20 27 views
20

2つのチャンネルをリッスンして、両方のチャンネルが空になったときにブロックすることを希望します。しかし、両方のチャンネルにデータが含まれている場合は、もう一方のチャンネルが処理される前に排水されることを望みます。Go select文の優先順位回避策回避策

以下の作業例では、を処理する前にすべてoutを流しておきたいと思います。私は優先順位を持たないselect -statementを使用します。問題を回避するにはどうしたらよいでしょうか?出口の前に10個のアウト・バリューをすべて処理しますか?

package main 

import "fmt" 

func sender(out chan int, exit chan bool){ 
    for i := 1; i <= 10; i++ { 
     out <- i 
    } 
    exit <- true 
} 

func main(){ 
    out := make(chan int, 10) 
    exit := make(chan bool) 

    go sender(out, exit) 

    L: 
    for { 
     select { 
      case i := <-out: 
       fmt.Printf("Value: %d\n", i) 
      case <-exit: 
       fmt.Println("Exiting") 
       break L 
     } 
    } 
    fmt.Println("Did we get all 10? Most likely not") 
} 

答えて

14
package main 

import "fmt" 

func sender(out chan int, exit chan bool) { 
    for i := 1; i <= 10; i++ { 
     out <- i 
    } 
    exit <- true 
} 

func main() { 
    out := make(chan int, 10) 
    exit := make(chan bool) 

    go sender(out, exit) 

    for { 
     select { 
     case i := <-out: 
      fmt.Printf("Value: %d\n", i) 
      continue 
     default: 
     } 
     select { 
     case i := <-out: 
      fmt.Printf("Value: %d\n", i) 
      continue 
     case <-exit: 
      fmt.Println("Exiting") 
     } 
     break 
    } 
    fmt.Println("Did we get all 10? I think so!") 
} 

最初の選択のデフォルトの場合は、非ブロック化されます。選択は出口チャネルを見ずに出口チャネルを排水しますが、それ以外の場合は待機しません。アウトチャネルが空の場合、すぐに2番目の選択に降ります。 2番目の選択はブロックしています。どちらかのチャンネルのデータを待ちます。出口が来たら、出口を処理してループを終了させます。データが来ると、ループの先頭に戻り、ドレインモードに戻ります。

+1

アイデアは私のものと非常に似ています。しかし、 'continue'ステートメントを使うと、フラグの必要がなくなります。スマート。さて、これはおそらく私が得ることができるように良い答えです。ありがとう! – ANisus

+2

アウトチャネルが閉じている場合、これは最初のselect文で無限ループします。 – jorelli

+1

jorelli、かなり真実。敵対的またはバグのあるゴルーチンが予期せずチャンネルを閉じることを許可したい場合は、受信時にokステータスをチェックします。 – Sonia

5

別のアプローチ:

package main 

import "fmt" 

func sender(c chan int) chan int { 
     go func() { 
       for i := 1; i <= 15; i++ { 
         c <- i 
       } 
       close(c) 
     }() 
     return c 
} 

func main() { 
     for i := range sender(make(chan int, 10)) { 
       fmt.Printf("Value: %d\n", i) 
     } 
     fmt.Println("Did we get all 15? Surely yes") 
} 

$ go run main.go 
Value: 1 
Value: 2 
Value: 3 
Value: 4 
Value: 5 
Value: 6 
Value: 7 
Value: 8 
Value: 9 
Value: 10 
Value: 11 
Value: 12 
Value: 13 
Value: 14 
Value: 15 
Did we get all 15? Surely yes 
$ 
+1

ありがとう!私があなたを正しく理解しているならば、チャンネルを1つだけ使用し、チャンネルを閉じて出口を呼び出し、 'for range'ステートメントを破ることをお勧めします。確かに、これはもっと良い方法ですが、私の場合は2つのチャンネルで作業しています。 – ANisus

1

私は1つの、むしろ簡単な回避策を作成しました。それは私が欲しいものを行いますが、他の誰がよりよい解決策を持っている場合、私に知らせてください。代わりに、受信に出たの

exiting := false 
for !exiting || len(out)>0 { 
    select { 
     case i := <-out: 
      fmt.Printf("Value: %d\n", i) 
     case <-exit: 
      exiting = true 
      fmt.Println("Exiting") 
    } 
} 

、私は何もchan outに残っていないことを確認しましたしたら出て行く、終了フラグを立てます。

+1

これはうまくコンパクトですが、一般的に回避しようとするべきいくつかのトリックを使用します。プログラムが大きくなると、フラグが混乱する。彼らは一種のものだ。もっと真剣に、len(chan)はしばしばレースを紹介することができます。この状況では大丈夫ですが、多くの場合、あなたが行動を起こす前に変更できるので、len(chan)に基づいて決定することは無効です。あなたがlen == 0を得た後、値が到着した後、出口が到着し、出口が選択された場合を想像してください。あなたは肩をすくめて、彼らがほぼ同じ時間に到着したと言いますが、時には批判的なプログラムでは、それは問題になるかもしれません。 – Sonia

+0

うーん、私は説明した場合でもそれはまだ動作します。申し訳ありませんが悪い例です。しかし、とにかく、私は同期コードでlenの使用を避けようとしています。 – Sonia

+0

こんにちはソニア:)。良い入力。はい、私の場合はそれほど重要ではありません。私は出かける前に外出していたものを洗い流したいと思っていました。しかし、実際には、代わりに 'for range'と' close(out) 'を使ってコードをredidします(jmnlで示唆されているように)。次に、終了に先行するチャネルパイプに配置されたアウトイベントのみが「フラッシュ」されます。ナスダックが私に彼らのためにいくつかのGoプログラムを実行するように依頼すれば、私はlen(chan)に基づいた意思決定を避けるでしょう; – ANisus

26

この言語はこれをネイティブでサポートしており、回避策は必要ありません。これは非常に簡単です。終了チャネルはプロデューサにのみ表示する必要があります。終了すると、プロデューサはチャネルを閉じます。チャネルが空で閉じているときだけ消費者は終了する。これは次のようにチャネルから読み出すことにより可能となる。

v, ok := <-c 

この値vが実際チャネル(ok == true)を読み取ったかどうかを示すブール値をokを設定するか、またはvに設定された場合cが閉じて空(ok == false)なので、チャネルcによって処理される型のゼロ値。チャネルが閉じて空でない場合、vは有効な値になり、oktrueになります。チャネルが閉じて空の場合、vはチャネルcによって処理されるタイプのゼロ値になり、okfalseになり、vは役に立たないことを示します。ここで

は説明する例です。

package main 

import (
    "fmt" 
    "math/rand" 
    "time" 
) 

var (
    produced = 0 
    processed = 0 
) 

func produceEndlessly(out chan int, quit chan bool) { 
    defer close(out) 
    for { 
     select { 
     case <-quit: 
      fmt.Println("RECV QUIT") 
      return 
     default: 
      out <- rand.Int() 
      time.Sleep(time.Duration(rand.Int63n(5e6))) 
      produced++ 
     } 
    } 
} 

func quitRandomly(quit chan bool) { 
    d := time.Duration(rand.Int63n(5e9)) 
    fmt.Println("SLEEP", d) 
    time.Sleep(d) 
    fmt.Println("SEND QUIT") 
    quit <- true 
} 

func main() { 
    vals, quit := make(chan int, 10), make(chan bool) 
    go produceEndlessly(vals, quit) 
    go quitRandomly(quit) 
    for { 
     x, ok := <-vals 
     if !ok { 
      break 
     } 
     fmt.Println(x) 
     processed++ 
     time.Sleep(time.Duration(rand.Int63n(5e8))) 
    } 
    fmt.Println("Produced:", produced) 
    fmt.Println("Processed:", processed) 
} 

これは、外出先仕様の「演算子を受け取る」セクションに記載されています。私の場合はhttp://golang.org/ref/spec#Receive_operator

+0

これはまさに私が探していた解決策であり、Soniaの答えにある潜在的な競合状態のバグはありません – BrandonAGr

0

、私は本当に1からのデータを優先させたかったですチャネルを別のチャネルよりも優先し、帯域外の出口信号を持つだけではありません。私は、このアプローチは、潜在的な競合状態ずにうまくいくと思う同じ問題を持つ他の誰の利益のために:

OUTER: 
for channelA != nil || channelB != nil { 

    select { 

    case typeA, ok := <-channelA: 
     if !ok { 
      channelA = nil 
      continue OUTER 
     } 
     doSomething(typeA) 

    case nodeIn, ok := <-channelB: 
     if !ok { 
      channelB = nil 
      continue OUTER 
     } 

     // Looped non-blocking nested select here checks that channelA 
     // really is drained before we deal with the data from channelB 
     NESTED: 
     for { 
      select { 
      case typeA, ok := <-channelA: 
       if !ok { 
        channelA = nil 
        continue NESTED 
       } 
       doSomething(typeA) 

      default: 
       // We are free to process the typeB data now 
       doSomethingElse(typeB) 
       break NESTED 
      } 
     } 
    } 

} 
0

私はソニアの答えはincorrect.Thisだと思う私の解決策、少し複雑です。

package main 

import "fmt" 

func sender(out chan int, exit chan bool){ 
    for i := 1; i <= 10; i++ { 
     out <- i 
    } 
    exit <- true 
} 

func main(){ 
    out := make(chan int, 10) 
    exit := make(chan bool) 

    go sender(out, exit) 

    L: 
    for { 
     select { 
      case i := <-out: 
       fmt.Printf("Value: %d\n", i) 
      case <-exit: 
       for{ 
        select{ 
        case i:=<-out: 
         fmt.Printf("Value: %d\n", i) 
        default: 
         fmt.Println("Exiting") 
         break L 
        } 
       } 
       fmt.Println("Exiting") 
       break L 
     } 
    } 
    fmt.Println("Did we get all 10? Yes!") 
} 
0

バッファリングされたチャネルを使用する特定の理由はありますか?make(chan int, 10)

使用しているバッファなしのチャネルとバッファされたチャネルを使用する必要があります。

10を削除すると、ちょうどmake(chan int)になります。

sender機能で実行が唯一後exit <- true声明outチャネルからの最後のメッセージを進めることができますこの方法ではi := <-out文によってデキューされます。その文が実行されていない場合、ゴルーチンではexit <- trueに到達する方法はありません。

0

別のオプションがあります。

コンシューマコード:提案のための

go func() { 
    stop := false 
    for { 
     select { 
     case item, _ := <-r.queue: 
     doWork(item) 
     case <-r.stopping: 
     stop = true 
     } 
     if stop && len(r.queue) == 0 { 
     break 
     } 
    } 
    }()