web-dev-qa-db-ja.com

Goselectステートメントの回避策の優先順位

2つのチャネルをリッスンし、両方のチャネルが空になるとブロックされるgoルーチンが必要です。ただし、両方のチャネルにデータが含まれている場合は、一方をドレインしてからもう一方を処理する必要があります。

以下の作業例では、outが処理される前にすべてのexitが排出されることを望みます。優先順位のないselect-ステートメントを使用します。終了前に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")
}
33
ANisus
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番目の選択はブロッキングです。いずれかのチャネルでデータを待機します。出口が来ると、それを処理し、ループを終了できるようにします。データが来ると、ループの先頭に戻り、ドレインモードに戻ります。

22
Sonia

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

v, ok := <-c

これにより、okがブール値に設定され、値vが実際にチャネルから読み取られたかどうかが示されます(ok == true)、またはvが閉じて空であるため、cがチャネルcによって処理されるタイプのゼロ値に設定された場合(ok == false)。チャネルが閉じていて空でない場合、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)
}

これは、go仕様の「ReceiveOperator」セクションに記載されています: http://golang.org/ref/spec#Receive_operator

33
jorelli

別のアプローチ:

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
$ 
5
zzzz

かなり単純な回避策を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
ANisus

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

消費者コード:

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

ソニアの答えは間違っていると思います。これは私の解決策であり、少し複雑です。

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ステートメントにのみ進むことができますafteroutチャネルからの最後のメッセージはi := <-outステートメントによってデキューされます。そのステートメントが実行されていない場合、ゴルーチンでexit <- trueに到達する方法はありません。

0
George Polevoy

私の場合、帯域外の出口信号だけでなく、あるチャネルからのデータを別のチャネルよりも優先したかったのです。同じ問題を抱えている他の人の利益のために、このアプローチは潜在的な競合状態なしで機能すると思います。

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
monoi