コンテンツ
1.チャネルブロッキング/デッドロックの例
デッドロック:すべてのスレッドまたはプロセスがリソースの解放を待機しています
1)デッドロックの例1
func main() {
ch := make(chan int)
<- ch // 阻塞main goroutine, 信道ch被锁
}
//执行这个程序你会看到Go报这样的错误:
fatal error: all goroutines are asleep - deadlock!
上記のプログラムでは、ゴルーチンは1つしかないため、データを追加したり、データを保存したりすると、チャネルがロックされ、現在のゴルーチンがブロックされます。
2)デッドロックの例2
var ch1 chan int = make(chan int)
var ch2 chan int = make(chan int)
func say(s string) {
fmt.Println(s)
ch1 <- <- ch2 // ch1 等待 ch2流出的数据
}
func main() {
go say("hello")
<- ch1 // 堵塞主线
}
その中で、メインラインはch1のデータが流出するのを待ち、ch1はch2のデータが流出するのを待ちますが、ch2はデータが流入するのを待ち、両方のゴルーチンが待機します。つまり、デッドロックです。
3)デッドロックの概要
デフォルトでは、チャネルの保存およびフェッチメッセージはブロックされています。つまり、バッファリングされていないチャネルは、もう一方の端の準備ができていない限り、メッセージをフェッチおよび保存するときに現在のゴルーチンを一時停止します。
バッファリングされていないチャネルに流入と流出がない場合、または流入と流出がない場合、デッドロックが発生します。または、Goによって開始されたすべてのゴルーチンの非バッファチャネルは、データを1行に格納し、データを1行でフェッチする必要があり、それらをペアにする必要があることを理解してください。ペアになっていないほとんどすべてのチャネルアクセスデータはデッドロックしますが、次のような特別なデータもあります。
func main() {
c := make(chan int)
go func() {
c <- 1
}()
}
プログラムは正常に終了します。非常に単純です。要約が機能しないわけではありません。メインは他のゴルーチンを待たずに最初に実行を終了したため、cチャネルにデータが流れませんでした。合計1つのゴルーチンが実行されました。ブロッキングがなかったため、デッドロックエラーは発生しませんでした。チャネルがメインラインをブロックできるようにすると言われています(<-c)
第二に、デッドロックソリューション
1.方法1:最初にチャネルを消費します
package main
import (
"fmt"
)
func f1(in chan int) {
fmt.Println(<-in)
}
func main() {
out := make(chan int)
//调整下位置,先消费
go f1(out)
out <- 2
}
2.方法2:バッファチャネルを使用する
バッファを追加すると、バッファチャネルになります。バッファチャネルには容量があります。データを保存する場合は、現在の回線をブロックしたり、データが削除されるのを待たずに、最初にデータをチャネルに配置できます。バッファチャネルがフル状態に達すると、この時点でそれ以上データを伝送できなくなるため、「データを流入する前にデータを削除する必要があります」とブロックが表示されます。
バッファリングされたチャネルchは、バッファリングされていない3つの要素をストリーミングできます
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
}
別のデータを流そうとすると、チャネルchがメインラインをブロックし、デッドロックを報告します。つまり、バッファチャネルがいっぱいになるとロックされます。
3.チャネルデータのインおよびアウトシーケンス
1.バッファなしチャネル
var ch chan int = make(chan int)
func foo(id int) { //id: 这个routine的标号
ch <- id
}
func main() {
// 开启5个routine
for i := 0; i < 5; i++ {
go foo(i)
}
// 取出信道中的数据
for i := 0; i < 5; i++ {
fmt.Print(<- ch)
}
}
バッファリングされていないチャネルのデータが先入れ先出しですが、バッファリングされていないチャネルはデータを格納せず、データのフローのみを担当します
2.バッファチャネル
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
fmt.Println(<-ch) // 3
}
バッファチャネルも先入れ先出しであり、バッファチャネルはスレッドセーフキューと考えることができます
4.マルチゴルンティンソリューションを待っています
さて、元の問題に戻りましょう。チャネルを使用してメインラインをブロックし、すべてのゴルーチンがなくなるのを待ちます。
これは、多くの小さなゴルーチンを開き、それぞれが独自に実行し、終了すると最終的にメインラインに報告するモデルです。
スキームの次の2つのバージョンについて説明します。
- バッファリングされていないチャネルを1つだけ使用してメインラインをブロックします
- ゴルーチンの数に等しい容量のバッファリングされたチャネルを使用します
シナリオ1の場合、サンプルコードは次のようになります。
var quit chan int // 只开一个信道
func foo(id int) {
fmt.Println(id)
quit <- 0 // ok, finished
}
func main() {
count := 1000
quit = make(chan int) // 无缓冲
for i := 0; i < count; i++ {
go foo(i)
}
for i := 0; i < count; i++ {
<- quit
}
}
オプション2の場合、チャネルをバッファ1000に変更します。
quit = make(chan int, count) // 容量1000
実際、唯一の違いは、一方がバッファリングされ、もう一方がバッファリングされていないことです。
このシナリオでは、両方がタスクを実行でき、どちらも問題ありません。
- バッファリングされていないチャネルは、1つずつ「流入および流出」するデータのバッチです。
- バッファチャネルは1つずつ保存され、一緒にストリーミングされます