web-dev-qa-db-ja.com

同じゴルーチンでバッファされていないチャネルを使用するとデッドロックが発生する理由

この些細な状況については簡単な説明があると確信していますが、go同時実行モデルは初めてです。

この例を実行すると

package main

import "fmt"

func main() {
    c := make(chan int)    
    c <- 1   
    fmt.Println(<-c)
}

私はこのエラーを受け取ります:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
    /home/tarrsalah/src/go/src/github.com/tarrsalah/tour.golang.org/65.go:8 +0x52
exit status 2

どうして ?


ラッピングc <-内のgoroutineは、サンプルを期待どおりに実行します

package main

import "fmt"

func main() {
    c := make(chan int)        
    go func(){
       c <- 1
    }()
    fmt.Println(<-c)
}

繰り返しますが、なぜですか?

デッドロックを解消してコードを修正する方法だけでなく、詳細な説明が必要です。

42
tarrsalah

ドキュメント から:

チャネルがバッファされていない場合、送信者は受信者が値を受信するまでブロックします。チャネルにバッファがある場合、送信者は値がバッファにコピーされるまでのみブロックします。バッファがいっぱいの場合、これは、一部の受信者が値を取得するまで待機することを意味します。

そうでなければ言った:

  • チャンネルがいっぱいになると、送信者は別のゴルーチンが受信することでスペースを空けるのを待ちます
  • バッファされていないチャネルは常に完全なチャネルであることがわかります。送信者が送信したものを取得する別のゴルーチンが必要です。

この行

c <- 1

チャネルがバッファリングされていないためブロックします。値を受け取るゴルーチンは他にないため、状況は解決できません。これはデッドロックです。

チャンネル作成を次のように変更することで、ブロックしないようにすることができます

c := make(chan int, 1) 

そのため、ブロックする前にチャネル内の1つのアイテムのためのスペースがあります。

しかし、それは並行性の問題ではありません。通常、中に入れたものを処理するために他のゴルーチンのないチャンネルを使用することはありません。次のような受信ゴルーチンを定義できます。

func main() {
    c := make(chan int)    
    go func() {
        fmt.Println("received:", <-c)
    }()
    c <- 1   
}

デモ

70
Denys Séguret

バッファリングされていないチャネルでは、データの受信を待機しているレシーバが必要になるまでチャネルへの書き込みは行われません。これは、以下の例で意味します

func main(){
    ch := make(chan int)
    ch <- 10   /* Main routine is Blocked, because there is no routine to receive the value   */
    <- ch
}

さて、他のgoルーチンがある場合、同じ原則が適用されます

func main(){
  ch :=make(chan int)
  go task(ch)
  ch <-10
}
func task(ch chan int){
   <- ch
}

これは、taskルーチンがデータが消費されるのを待ってから、バッファリングされていないチャネルへの書き込みが発生するため、機能します。

より明確にするために、main関数の2番目と3番目のステートメントの順序を入れ替えましょう。

func main(){
  ch := make(chan int)
  ch <- 10       /*Blocked: No routine is waiting for the data to be consumed from the channel */
  go task(ch)
}

これはデッドロックにつながります

つまり、バッファリングされていないチャネルへの書き込みは、チャネルからの読み取りを待機しているルーチンがある場合にのみ発生します。そうでない場合、書き込み操作は永久にブロックされ、デッドロックにつながります。

[〜#〜] note [〜#〜]:バッファリングされたチャネルにも同じ概念が適用されますが、送信者はバッファがいっぱいになるまでブロックされません。操作。

したがって、サイズ1のバッファリングされたチャネルがある場合、上記のコードは機能します

func main(){
  ch := make(chan int, 1) /*channel of size 1 */
  ch <-10  /* Not blocked: can put the value in channel buffer */
  <- ch 
}

しかし、上記の例にさらに値を書き込むと、デッドロックが発生します

func main(){
  ch := make(chan int, 1) /*channel Buffer size 1 */
  ch <- 10
  ch <- 20 /*Blocked: Because Buffer size is already full and no one is waiting to recieve the Data  from channel */
  <- ch
  <- ch
}
10
bharatj

この回答では、チャネルとゴルーチンの観点からgoがどのように機能するかを少し覗くことができるエラーメッセージを説明しようとします

最初の例は次のとおりです。

package main

import "fmt"

func main() {
    c := make(chan int)    
    c <- 1   
    fmt.Println(<-c)
}

エラーメッセージは次のとおりです。

fatal error: all goroutines are asleep - deadlock!

コードにはゴルーチンはまったくありません(ところで、このエラーはコンパイル時ではなくランタイムにあります)。 goがこの行を実行するとc <- 1、チャネル内のメッセージがどこかで受信されることを確認したい(つまり<-c)。 Goは、この時点でチャネルが受信されるかどうかを知りません。 goは、次のいずれかが発生するまで、実行中のゴルーチンが終了するのを待ちます。

  1. すべてのゴルーチンが終了しました(眠っています)
  2. ゴルーチンの1つがチャネルを受信しようとします

ケース#1では、goは上記のメッセージでエラーを出力します。これは、goroutineがチャネルを受信し、チャネルを必要とする方法がないことを知っているためです。

ケース#2では、プログラムは続行されます。これで、このチャネルが受信されたことがわかります。これは、OPの例で成功したケースを説明しています。

1
Chun Yang
  • バッファリングは同期を削除します。
  • バッファリングにより、Erlangのメールボックスのようになります。
  • バッファされたチャネルはいくつかの問題にとって重要になる可能性がありますが、
  • デフォルトでは、チャネルはバッファなしです。つまり、送信のみを受け入れます。
    (chan <-)送信された値を受信する準備ができている対応する受信(<-chan)がある場合。
  • バッファされたチャネルは、それらの値に対応するレシーバーなしで、限られた数の値を受け入れます。

messages:= make(chan string、2)//-最大2つの値をバッファリングする文字列のチャネル。

チャネルでの基本的な送受信がブロックされています。ただし、select句をdefault句とともに使用して、非ブロッキング送信、受信、さらには非ブロッキングのマルチウェイselectsを実装できます。 。

0