私はのような複数の非バッファリングチャネルで選択を使用するときに見つかりました
select {
case <- chana:
case <- chanb:
}
両方のチャネルにデータがある場合でも、この選択を処理するときに、ケースチャナとケースチャンブに該当する呼び出しのバランスが取れていません。
package main
import (
"fmt"
_ "net/http/pprof"
"sync"
"time"
)
func main() {
chana := make(chan int)
chanb := make(chan int)
go func() {
for i := 0; i < 1000; i++ {
chana <- 100 * i
}
}()
go func() {
for i := 0; i < 1000; i++ {
chanb <- i
}
}()
time.Sleep(time.Microsecond * 300)
acount := 0
bcount := 0
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
for {
select {
case <-chana:
acount++
case <-chanb:
bcount++
}
if acount == 1000 || bcount == 1000 {
fmt.Println("finish one acount, bcount", acount, bcount)
break
}
}
wg.Done()
}()
wg.Wait()
}
このデモを実行します。chana、chanbの1つが読み取り/書き込みを終了すると、他は999-1のままになる場合があります。
バランスをとる方法はありますか?
関連トピックが見つかりました
golang-channels-select-statement
Go select
ステートメントは、(ready)ケースに偏っていません。仕様からの引用:
1つ以上の通信を続行できる場合は、均一な擬似ランダム選択を介して、続行できる単一の通信が選択されます。それ以外の場合で、デフォルトのケースがある場合は、そのケースが選択されます。デフォルトのケースがない場合、「select」ステートメントは、少なくとも1つの通信が続行できるまでブロックされます。
複数の通信を続行できる場合は、ランダムに1つが選択されます。これは完全なランダム分布ではありません。仕様ではそれが保証されていませんが、ランダムです。
あなたが経験するのは、Go PlaygroundがGOMAXPROCS=1
( ここで確認できます )で、goroutineスケジューラがプリエンプティブではない結果です。これが意味することは、デフォルトではゴルーチンは並列実行されないということです。ブロッキング操作が発生すると(たとえば、ネットワークからの読み取り、またはブロッキングしているチャネルでの受信または送信の試行など)、実行の準備ができている別のゴルーチンが続行されると、ゴルーチンがパークに配置されます。
また、コードにはブロッキング操作がないため、ゴルーチンはパークに配置されず、「プロデューサー」ゴルーチンの1つだけが実行され、もう一方はスケジュールされない可能性があります。
GOMAXPROCS=4
というローカルコンピューターでコードを実行すると、非常に「現実的な」結果が得られます。数回実行すると、出力は次のようになります。
finish one acount, bcount 1000 901
finish one acount, bcount 1000 335
finish one acount, bcount 1000 872
finish one acount, bcount 427 1000
1つのケースに優先順位を付ける必要がある場合は、この回答を確認してください: go selectステートメントの優先度を強制
select
のデフォルトの動作は、同じ優先順位を保証するものではありませんが、平均的にはそれに近いものになります。同等の優先度を保証する必要がある場合は、select
を使用しないでください。ただし、次のような2つのチャネルからの2つの非ブロッキング受信のシーケンスを実行できます。
for {
select {
case <-chana:
acount++
default:
}
select {
case <-chanb:
bcount++
default:
}
if acount == 1000 || bcount == 1000 {
fmt.Println("finish one acount, bcount", acount, bcount)
break
}
}
上記の2つの非ブロッキング受信は、両方の供給値の場合、2つのチャネルを同じ速度で(同じ優先度で)ドレインします。一方が供給しない場合、他方は遅延またはブロックされることなく常に受信されます。
これについて1つ注意する点は、チャネルのnoneが受信する値を提供する場合、これは基本的に「ビジー」ループになるため、計算能力を消費することです。これを回避するために、準備ができているチャネルがないことを検出し、その後両方の受信でselect
ステートメントを使用します。それらのうち、CPUリソースを浪費することなく受信する準備ができています。
for {
received := 0
select {
case <-chana:
acount++
received++
default:
}
select {
case <-chanb:
bcount++
received++
default:
}
if received == 0 {
select {
case <-chana:
acount++
case <-chanb:
bcount++
}
}
if acount == 1000 || bcount == 1000 {
fmt.Println("finish one acount, bcount", acount, bcount)
break
}
}
Goroutineスケジューリングの詳細については、次の質問を参照してください。
Goroutines 8kbおよびWindows OSスレッド1 mb
golangでのファイルの書き込みで多数のゴルーチンがブロックされていると、多くのスレッドが作成されないのはなぜですか?
コメントで述べたように、バランスを確保したい場合は、読み取りゴルーチンでselect
を完全に使用せずに、バッファリングされていないチャネルによって提供される同期に依存することができます。
go func() {
for {
<-chana
acount++
<-chanb
bcount++
if acount == 1000 || bcount == 1000 {
fmt.Println("finish one acount, bcount", acount, bcount)
break
}
}
wg.Done()
}()
編集済み:供給側からもバランスをとることができますが、@ iczaの答えはこれよりも私にとってより良いオプションであるように思われ、そもそもこれを引き起こしていたスケジューリングについても説明しています。驚いたことに、それは私の(仮想)マシン上でも一方的なものでした。
これは、供給側から2つのルーチンのバランスをとることができるものです(どういうわけかPlaygroundでは動作しないようです)。
_package main
import (
"fmt"
_ "net/http/pprof"
"sync"
"sync/atomic"
"time"
)
func main() {
chana := make(chan int)
chanb := make(chan int)
var balanceSwitch int32
go func() {
for i := 0; i < 1000; i++ {
for atomic.LoadInt32(&balanceSwitch) != 0 {
fmt.Println("Holding R1")
time.Sleep(time.Nanosecond * 1)
}
chana <- 100 * i
fmt.Println("R1: Sent i", i)
atomic.StoreInt32(&balanceSwitch, 1)
}
}()
go func() {
for i := 0; i < 1000; i++ {
for atomic.LoadInt32(&balanceSwitch) != 1 {
fmt.Println("Holding R2")
time.Sleep(time.Nanosecond * 1)
}
chanb <- i
fmt.Println("R2: Sent i", i)
atomic.StoreInt32(&balanceSwitch, 0)
}
}()
time.Sleep(time.Microsecond * 300)
acount := 0
bcount := 0
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
for {
select {
case <-chana:
acount++
case <-chanb:
bcount++
}
fmt.Println("Acount Bcount", acount, bcount)
if acount == 1000 || bcount == 1000 {
fmt.Println("finish one acount, bcount", acount, bcount)
break
}
}
wg.Done()
}()
wg.Wait()
}
_
atomic.LoadInt32(&balanceSwitch) != XX
およびatomic.StoreInt32(&balanceSwitch, X)
、またはその他のメカニズムを変更することにより、任意の数のルーチンにマッピングできます。最善の方法ではないかもしれませんが、それが要件である場合は、そのようなオプションを検討する必要があります。お役に立てれば。