Go 言語は、共有メモリの代わりに通信方法の使用を推奨しており、ゴルーチン間でリソースを共有する必要がある場合、チャネルはゴルーチン間にパイプラインを設定し、データの同期交換を保証するメカニズムを提供します。チャネルを宣言するときは、共有するデータのタイプを指定する必要があります。組み込み型、名前付き型、構造体型、および参照型の値またはポインターは、チャネルを通じて共有できます。
ここでの通信方法は、次の図に示すようにチャネルを使用します。
地下鉄の駅、カフェテリア、トイレなどの公共の場所に人が多い場合、誰もが行列に並ぶ習慣が身に付きますが、その目的は、混雑や行列ジャンプによる非効率なリソースの使用と交換プロセスを避けることです。コードやデータも同様で、データを奪い合うために複数のゴルーチンが存在すると実行効率が低くなりますが、キューを利用するのが最も効率的です。チャネルはキューのような構造になっています。
チャネルのプロパティ
Go のチャネルは特殊なタイプです。一度にチャネルにアクセスしてデータを送信および取得できるのは、一度に 1 つの goroutine だけです。 Goroutine はチャネルを通じて通信できます。
チャネルはコンベア ベルトやキューのようなもので、データの送受信の順序を保証するために常に先入れ先出し (先入れ先出し) ルールに従います。
チャネルタイプを宣言する
スライス タイプが要素タイプを識別する必要があるのと同様に、チャネル自体にも装飾するタイプが必要です。チャネルの要素タイプは、チャネル内で送信されるデータのタイプであり、次のように宣言されます。
var 通道变量 chan 通道类型
- チャネルタイプ: チャネル内のデータのタイプ。
- チャネル変数: チャネルを保持する変数。
chan 型の空の値は nil であり、make で宣言した後でのみ使用できます。
チャンネルを作成する
チャネルは参照型であり、make で作成する必要があります。形式は次のとおりです。
チャネル インスタンス := make(chan データのタイプ)
- データのタイプ: チャネルで送信される要素のタイプ。
- チャネル インスタンス: make によって作成されたチャネル ハンドル。
以下の例を参照してください。
ch1 := make(chan int) // 整数型のチャネルを作成する
ch2 := make(chan interface{}) // 空のインターフェース型のチャネルを作成する。任意の形式を保存できる
type Equip struct{ /* いくつかのフィールド */ }
ch2 := make(chan *Equip) // Equipポインタ型のチャネルを作成する。*Equipを保存することができます。
チャネルを使用してデータを送信する
チャネルが作成されると、送信および受信操作に使用できるようになります。
1) チャネルによって送信されるデータの形式
チャネルは特別な演算子<-
を使用して送信され、チャネル経由でデータを送信する形式は次のとおりです。
通道变量 <- 值
- チャネル変数: make によって作成されたチャネル インスタンス。
- 値: 変数、定数、式、関数の戻り値などを指定できます。値のタイプは、ch チャネルの要素タイプと一致している必要があります。
2) チャネルを介してデータを送信する例
make を使用してチャネルを作成した後、 <-
を使用してチャネルにデータを送信できます。コードは次のとおりです。
// 空のインターフェースチャネルを作成する
ch := make(chan interface{})
// 0をチャネルに入れる
ch <- 0
// "hello"文字列をチャネルに入れる
ch <- "hello"
3) データが受信されるまで送信はブロックされ続けます。
チャネルにデータを送信するときに、受信側がデータを受信しない場合、送信操作はブロックされ続けます。 Go プログラムの実行中に、正常に送信できないステートメントをインテリジェントに検出し、プロンプトを表示します。コードは次のとおりです。
package main
func main() {
// 整数のチャネルを作成する
ch := make(chan int)
// 0をチャネル経由で送信しようとする
ch <- 0
}
コードを実行してエラーを報告します。
fatal error: all goroutines are asleep – deadlock!
エラー メッセージは、すべての goroutine (main を含む) が実行時に goroutine を待機していることが判明したことを意味します。つまり、すべてのゴルーチンのチャネルは、送信および受信に対応するコードを形成しません。
チャネル受信にも<-
演算子が使用され、チャネル受信には次の特性があります。
① チャネルの送受信操作は、2 つの異なるゴルーチン間で実行されます。
チャネルのデータが受信側で処理されない場合、チャネルのデータ送信側はブロックを継続するため、チャネルの受信は別のゴルーチンで実行する必要があります。
②送信者がデータを送信するまで受信はブロックされ続けます。
受信者が受信するときにデータを送信する送信者がチャネル内にいない場合、受信者も送信者がデータを送信するまでブロックされます。
③ 要素を 1 つずつ受信します。
チャネルは一度に 1 つのデータ要素のみを受信できます。
チャネルが受信したデータを書き込む方法は 4 つあります。
1) データ受信をブロックする
ブロッキング モードでデータを受信する場合、受信した変数は<-
演算子の左側の値として使用され、形式は次のとおりです。
data := <-ch
このステートメントが実行されると、データが受信されてデータ変数に割り当てられるまでブロックされます。
2) データをノンブロッキングで受信する
非ブロッキング メソッドを使用してチャネルからデータを受信する場合、ステートメントはブロックされず、形式は次のようになります。
data, ok := <-ch
- data:受信したデータを示します。データが受信されない場合、データはチャネル タイプのゼロ値です。
- ok: データを受信したかどうかを示します。
ノンブロッキング チャネル受信方法では CPU 使用率が高くなる可能性があるため、使用されることはほとんどありません。受信タイムアウト検出を実装する必要がある場合は、セレクトおよびタイマーチャネルと連携して、以下の内容を参照してください。
3) 任意のデータを受信し、受信したデータを無視します
データの受信をブロックした後、チャネルから返されたデータは無視されます。形式は次のとおりです。
<-ch
このステートメントの実行はデータを受信するまでブロックされますが、受信したデータは無視されます。このメソッドは実際には、チャネルを介したゴルーチン間の送受信をブロックすることによってのみ同時同期を実現します。
同時同期にチャネルを使用するには、次の例を参照できます。
package main
import (
"fmt"
)
func main() {
// チャネルを構築する
ch := make(chan int)
// 並行無名関数を開始する
go func() {
fmt.Println("start goroutine")
// チャネルを通じてmainのgoroutineに通知する
ch <- 0
fmt.Println("exit goroutine")
}()
fmt.Println("wait goroutine")
// 無名goroutineを待機する
<-ch
fmt.Println("all done")
}
コードを実行すると、出力は次のようになります。
wait goroutine
start goroutine
exit goroutine
all done
コードの説明は次のとおりです。
- 10 行目は、同期用のチャネルを構築します。
- 13 行目では、匿名関数の同時実行を開始します。
- 18行目、匿名ゴルーチンが終了しそうになったら、チャネルを通じてメインゴルーチンに通知します。この文はメインゴルーチンが受信するまでブロックされます。
- 27 行目では、ゴルーチンを開始した後、匿名ゴルーチンがチャネルを通じて終了するのをすぐに待ちます。
4) ループ受信
チャネルのデータ受信では、for range ステートメントを使用して複数の要素の受信操作を実行できます。その形式は次のとおりです。
chからdataを読み込む。
チャネル ch はトラバースでき、トラバースの結果が受信データになります。データの種類は、チャネルのデータの種類です。トラバーサルによって取得される変数は 1 つだけです。つまり、上の例ではデータです。
チャネル データのトラバースの例については、次のコードを参照してください。
チャネルからデータを受信するには for を使用します。
package main
import (
"fmt"
"time"
)
func main()
//チャネルを構築する
ch := make(chan int)
//匿名関数を並行して開始する
go func(){
//3から0までのループ
for i:= 3; i> = 0; i-- {
// 3から0の間の値を送信する
ch <- i
//送信が完了したごとに待機する
time.Sleep(time.Second)
}
}()
//チャネルデータを反復処理する
for data:= range ch {
//チャネルデータを印刷する
fmt.Println(data)
//データ0に到達した場合、受信ループを終了する
if data == 0 {
ブレーク
}
}
}
コードを実行すると、出力は次のようになります。
3
2
1
0
コードの説明は次のとおりです。
- 12 行目で、整数要素のチャネルが make によって生成されます。
- 15 行目では、匿名関数を同時に実行します。
- 18 行目で、ループを使用して 3 ~ 0 の値を生成します。
- 21行目では3から0までの値を順番にチャネルchに送信しています。
- 24 行目、各送信後に 1 秒間一時停止します。
- 30 行目で、for を使用してチャネルからデータを受信します。
- 33 行目、受信したデータを出力します。
- 36行目、値0を受信したら受信を停止します。送信を続けると、受信側の goroutine が終了しているため、チャネルに送信する goroutine がないため、ランタイムによってダウンタイム エラーがトリガーされます。