Channel と通信
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 60/107。
チャネルは、Goにおけるゴルーチン間の通信のための主要なメカニズムです。メモリを直接共有するのではなく、ゴルーチンはチャネルを通じて値を送受信します。これは、「メモリを共有することで通信するのではなく、通信することでメモリを共有せよ」というGoの哲学に従っています。
make関数とchanキーワードを使用してチャネルを作成します:
messages := make(chan string)値の送信と受信を行うには、矢印演算子 <- を使用します。矢印はデータの流れる方向を指します:
func main() {
messages := make(chan string)
go func() {
messages <- "hello" // チャネルに送信
}()
msg := <-messages // チャネルから受信
fmt.Println(msg) // 出力: hello
}デフォルトでは、送信と受信は相手側の準備ができるまでブロックされます。ゴルーチンが "hello" を送信すると、main の受信準備ができるまで待機します。このブロック動作により、組み込みの同期機能が提供されます。time.Sleep は必要ありません。
チャネルをクローズすることで、これ以上値が送信されないことを通知できます。受信側はこれを検知できます:
close(messages)
msg, ok := <-messages
if !ok {
fmt.Println("Channel closed")
}チャネルは型付けされています。chan string は文字列のみを扱うことができます。この型安全性により、ゴルーチンが正しいデータ型で通信することが保証され、実行時ではなくコンパイル時にエラーを捕捉できるようになります。
チャレンジ
簡単goroutineがチャネルを通じてどのように通信するかを実演する、メッセージリレーシステムを構築しましょう。各ステージが並行して動作し、メッセージが異なる処理ステージ間を流れるパイプラインを作成します。
コードは2つのファイルに分けて構成します:
relay.go: メッセージ処理パイプラインを定義します。ID(int) とContent(string) フィールドを持つMessage構造体を作成します。パイプラインの異なるステージを表す3つの関数を実装します:
Producer(messages []Message, out chan Message)- メッセージのスライスを受け取り、それぞれを出力チャネルに送信します。すべてのメッセージを送信した後、完了を知らせるためにチャネルを閉じます。Transformer(in chan Message, out chan Message)- 入力チャネルからメッセージを受信し、各メッセージの内容を大文字に変換して、変換されたメッセージを出力チャネルに送信します。入力チャネルが閉じられたこと(2つの戻り値を持つ受信形式を使用して検出)を確認したら、出力チャネルを閉じます。Consumer(in chan Message) []string- 入力チャネルからすべてのメッセージを受信し、それらをフォーマット済み文字列のスライスに収集します。各文字列はMessage [ID]: [Content]の形式に従う必要があります。チャネルが閉じられたら、そのスライスを返します。
main.go: パイプラインをセットアップし、並行するメッセージフローをオーケストレートします。メッセージの数を読み取り、次に各メッセージのIDと内容を読み取ります。メッセージを作成し、3つのパイプラインステージを接続するための2つのチャネルをセットアップします。ProducerとTransformerをgoroutineとして起動し、次にメインのgoroutineでConsumerを実行して結果を収集します。各結果を個別の行に出力します。
以下の入力が提供されます:
- 1行目:メッセージの数(整数)
- 続く行:各メッセージについて、メッセージID(整数)とその内容(文字列)の2行
例えば、以下が与えられた場合:
2
1
hello world
2
go channels出力は以下のようになります:
Message 1: HELLO WORLD
Message 2: GO CHANNELSパイプラインは次のように流れます:Producerがメッセージを送信 → Transformerが大文字に変換 → Consumerが結果を収集してフォーマット。各矢印は、並行するステージを接続するチャネルを表します。大文字への変換には strings パッケージを使用してください。
チートシート
チャネルは、ゴルーチン間で通信するための Go の主要なメカニズムです。chan キーワードと一緒に make を使用してチャネルを作成します。
messages := make(chan string)値の送信と受信には、矢印演算子 <- を使用します。矢印はデータの流れる方向を指します。
messages <- "hello" // チャネルへ送信
msg := <-messages // チャネルから受信デフォルトでは、送信と受信は相手側の準備ができるまでブロックされ、組み込みの同期機能を提供します。
func main() {
messages := make(chan string)
go func() {
messages <- "hello" // チャネルへ送信
}()
msg := <-messages // チャネルから受信
fmt.Println(msg) // Output: hello
}これ以上値が送信されないことを示すには、チャネルをクローズします。受信側は、2つの値を受け取る形式を使用してクローズを検知できます。
close(messages)
msg, ok := <-messages
if !ok {
fmt.Println("Channel closed")
}チャネルは型付けされています。chan string は文字列のみを運ぶことができ、コンパイル時の型安全性を提供します。
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// メッセージの数を読み込む
nLine, _ := reader.ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(nLine))
// メッセージを読み込む
messages := make([]Message, n)
for i := 0; i < n; i++ {
idLine, _ := reader.ReadString('\n')
id, _ := strconv.Atoi(strings.TrimSpace(idLine))
contentLine, _ := reader.ReadString('\n')
content := strings.TrimSpace(contentLine)
messages[i] = Message{ID: id, Content: content}
}
// TODO: パイプラインのステージを接続するための2つのチャネルを作成する
// TODO: Producerをゴルーチンとして起動する
// TODO: Transformerをゴルーチンとして起動する
// TODO: メインのゴルーチンでConsumerを実行し、結果を収集する
// TODO: 各結果を個別の行に出力する
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー