Menu
Coddy logo textTech

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 は文字列のみを扱うことができます。この型安全性により、ゴルーチンが正しいデータ型で通信することが保証され、実行時ではなくコンパイル時にエラーを捕捉できるようになります。

challenge icon

チャレンジ

簡単

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: 各結果を個別の行に出力する
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン