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")
}
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番目の選択はブロッキングです。いずれかのチャネルでデータを待機します。出口が来ると、それを処理し、ループを終了できるようにします。データが来ると、ループの先頭に戻り、ドレインモードに戻ります。
言語はこれをネイティブにサポートしており、回避策は必要ありません。非常に簡単です。終了チャネルはプロデューサーにのみ表示される必要があります。終了すると、プロデューサーはチャンネルを閉じます。チャネルが空で閉じている場合にのみ、コンシューマーは終了します。これは、次のようにチャネルから読み取ることで可能になります。
v, ok := <-c
これにより、ok
がブール値に設定され、値v
が実際にチャネルから読み取られたかどうかが示されます(ok == true
)、またはv
が閉じて空であるため、c
がチャネルc
によって処理されるタイプのゼロ値に設定された場合(ok == false
)。チャネルが閉じていて空でない場合、v
は有効な値になり、ok
はtrue
になります。チャネルが閉じて空の場合、v
はチャネルc
によって処理されるタイプのゼロ値になり、ok
はfalse
になります。 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
別のアプローチ:
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つ作成しました。それは私が望むことをしますが、他の誰かがより良い解決策を持っているなら、私に知らせてください:
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
に何も残っていないことを確認したら終了します。
別のオプションがあります。
消費者コード:
go func() {
stop := false
for {
select {
case item, _ := <-r.queue:
doWork(item)
case <-r.stopping:
stop = true
}
if stop && len(r.queue) == 0 {
break
}
}
}()
ソニアの答えは間違っていると思います。これは私の解決策であり、少し複雑です。
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!")
}
バッファリングされたチャネルmake(chan int, 10)
を使用する特別な理由はありますか?
使用しているバッファなしチャネルとバッファ付きチャネルを使用する必要があります。
10
を削除するだけで、make(chan int)
になります。
このように、sender
関数での実行は、exit <- true
ステートメントにのみ進むことができますafterout
チャネルからの最後のメッセージはi := <-out
ステートメントによってデキューされます。そのステートメントが実行されていない場合、ゴルーチンでexit <- true
に到達する方法はありません。
私の場合、帯域外の出口信号だけでなく、あるチャネルからのデータを別のチャネルよりも優先したかったのです。同じ問題を抱えている他の人の利益のために、このアプローチは潜在的な競合状態なしで機能すると思います。
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
}
}
}
}