100

複数のゴルーチンが同じチャネルで同時に受信しようとしています。チャネルで受信を開始する最後のゴルーチンが値を取得するようです。これは言語仕様のどこかにありますか、それとも未定義の動作ですか?

c := make(chan string)
for i := 0; i < 5; i++ {
    go func(i int) {
        <-c
        c <- fmt.Sprintf("goroutine %d", i)
    }(i)
}
c <- "hi"
fmt.Println(<-c)

出力:

goroutine 4

遊び場での例

編集:

思った以上に複雑だということがわかりました。メッセージはすべてのゴルーチンに渡されます。

c := make(chan string)
for i := 0; i < 5; i++ {
    go func(i int) {
        msg := <-c
        c <- fmt.Sprintf("%s, hi from %d", msg, i)
    }(i)
}
c <- "original"
fmt.Println(<-c)

出力:

original, hi from 0, hi from 1, hi from 2, hi from 3, hi from 4

注: 上記の出力は、Go の最近のバージョンでは古くなっています (コメントを参照)。

遊び場での例

4

6 に答える 6

85

はい、それは複雑ですが、物事をより簡単に感じさせる経験則がいくつかあります。

  • グローバル スコープでチャネルにアクセスする代わりに、go-routine に渡すチャネルに正式な引数を使用することを好みます。このようにして、より多くのコンパイラチェックを取得でき、モジュール性も向上します。
  • 特定の go-routine (「メイン」のものを含む)で同じチャネルで読み取りと書き込みの両方を回避します。そうしないと、デッドロックのリスクがはるかに高くなります。

これらの 2 つのガイドラインを適用した、別のバージョンのプログラムを次に示します。このケースは、チャネルに多くのライターと 1 つのリーダーがあることを示しています。

c := make(chan string)

for i := 1; i <= 5; i++ {
    go func(i int, co chan<- string) {
        for j := 1; j <= 5; j++ {
            co <- fmt.Sprintf("hi from %d.%d", i, j)
        }
    }(i, c)
}

for i := 1; i <= 25; i++ {
    fmt.Println(<-c)
}

http://play.golang.org/p/quQn7xePLw

これは、1 つのチャネルに書き込みを行う 5 つの go-routine を作成し、それぞれが 5 回書き込みます。メインの go-routine は 25 個のメッセージすべてを読み取ります。表示される順序が連続していないことが多いことに気付くかもしれません (つまり、並行性が明らかです)。

この例は、Go チャネルの機能を示しています。複数のライターが 1 つのチャネルを共有することが可能です。Go はメッセージを自動的にインターリーブします。

次の 2 番目の例に示すように、同じことが 1 つのチャネルの 1 つのライターと複数のリーダーに適用されます。

c := make(chan int)
var w sync.WaitGroup
w.Add(5)

for i := 1; i <= 5; i++ {
    go func(i int, ci <-chan int) {
        j := 1
        for v := range ci {
            time.Sleep(time.Millisecond)
            fmt.Printf("%d.%d got %d\n", i, j, v)
            j += 1
        }
        w.Done()
    }(i, c)
}

for i := 1; i <= 25; i++ {
    c <- i
}
close(c)
w.Wait()

この2 番目の例には、メインの goroutine に課された待機が含まれています。そうしないと、すぐに終了し、他の 5 つの goroutine が早期に終了してしまいます (この修正についてolovに感謝します) 。

どちらの例でも、バッファリングは必要ありませんでした。一般に、バッファリングをパフォーマンスの向上のみと見なすことは良い原則です。プログラムがバッファーなしでデッドロックしない場合は、バッファーでもデッドロックません(ただし、その逆常に真とは限りません)。したがって、別の経験則として、バッファリングなしで開始し、後で必要に応じて追加します

于 2013-03-30T17:30:15.747 に答える
29

返信が遅くなりましたが、これが将来、ロング ポーリング、「グローバル」ボタン、全員へのブロードキャストなどに役立つことを願っています。

効果的なGoは問題を説明しています:

受信側は、受信するデータが存在するまで常にブロックします。

つまり、1 つのチャネルをリッスンするゴルーチンを複数持つことはできず、すべてのゴルーチンが同じ値を受け取ることを期待できます。

このコード例を実行します。

package main

import "fmt"

func main() {
    c := make(chan int)

    for i := 1; i <= 5; i++ {
        go func(i int) {
        for v := range c {
                fmt.Printf("count %d from goroutine #%d\n", v, i)
            }
        }(i)
    }

    for i := 1; i <= 25; i++ {
        c<-i
    }

    close(c)
}

チャネルをリッスンしているゴルーチンが 5 つある場合でも、「count 1」が複数回表示されることはありません。これは、最初のゴルーチンがチャネルをブロックすると、他のすべてのゴルーチンが順番に待機する必要があるためです。チャネルのブロックが解除されると、カウントはすでに受信され、チャネルから削除されているため、次のゴルーチンが次のカウント値を取得します。

于 2013-11-06T17:16:07.573 に答える
7

それは複雑です。

また、 で何が起こるかを見てくださいGOMAXPROCS = NumCPU+1。例えば、

package main

import (
    "fmt"
    "runtime"
)

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU() + 1)
    fmt.Print(runtime.GOMAXPROCS(0))
    c := make(chan string)
    for i := 0; i < 5; i++ {
        go func(i int) {
            msg := <-c
            c <- fmt.Sprintf("%s, hi from %d", msg, i)
        }(i)
    }
    c <- ", original"
    fmt.Println(<-c)
}

出力:

5, original, hi from 4

そして、バッファリングされたチャネルで何が起こるかを見てください。例えば、

package main

import "fmt"

func main() {
    c := make(chan string, 5+1)
    for i := 0; i < 5; i++ {
        go func(i int) {
            msg := <-c
            c <- fmt.Sprintf("%s, hi from %d", msg, i)
        }(i)
    }
    c <- "original"
    fmt.Println(<-c)
}

出力:

original

これらのケースについても説明できるはずです。

于 2013-03-30T06:55:36.053 に答える