Go言語のchannelの基本!goroutine間のデータ通信を理解しよう
生徒
「先生、Go言語の“channel(チャネル)”ってよく聞くんですが、どんなものなんですか?」
先生
「良い質問ですね。channelは、Go言語で複数のgoroutine(ゴルーチン)が“安全にデータをやり取りする”ための仕組みなんです。」
生徒
「安全にデータをやり取り?なんだか難しそうです……」
先生
「大丈夫です!channelはとてもシンプルな考え方で、まるで“データを渡すパイプ”のようなものなんですよ。実際の例を見ながら覚えていきましょう!」
1. channel(チャネル)とは?
channelは、Go言語でgoroutine同士がデータを送受信するための「通信路(パイプ)」です。goroutineは同時に動作する複数の関数のようなもので、channelを使うことで安全にデータを受け渡しできます。
たとえば、誰かが「りんごを渡す箱」を持っていて、片方が「りんごを入れる」、もう片方が「りんごを取り出す」ようなイメージです。その箱がchannelの役割です。
Go言語では、make関数を使ってchannelを作成します。作成方法はとても簡単です。
ch := make(chan string)
このコードでは、「文字列(string)を送受信するためのchannel」を作成しています。chanの後ろに型を指定することで、送るデータの種類を決められます。
2. channelを使ったデータの送受信
channelを作ったら、<-(矢印)を使ってデータを送ったり受け取ったりします。
- 送信:
ch <- 値 - 受信:
変数 := <- ch
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "こんにちは、channel!"
}()
msg := <-ch
fmt.Println(msg)
}
このプログラムでは、別のgoroutineが"こんにちは、channel!"という文字列をchannelに送っています。メイン関数側はそのデータを受信して出力します。
こんにちは、channel!
このように、channelを使うことでgoroutine間のデータ通信がとてもシンプルに書けます。
3. channelの動作の仕組み
Goのchannelには大きく分けて2種類の動作があります。
- 同期的なchannel(バッファなし):送る側と受け取る側が“同時に存在”しないとデータが流れません。
- 非同期的なchannel(バッファあり):一定数のデータを貯めておくことができます。
package main
import "fmt"
func main() {
ch := make(chan string, 2) // バッファサイズ2のchannel
ch <- "A"
ch <- "B"
fmt.Println(<-ch)
fmt.Println(<-ch)
}
A
B
この例では、バッファサイズを「2」にしたことで、2つのデータを先に送っておくことができます。もしバッファがいっぱいになった状態でさらに送ろうとすると、空きができるまで待機します。
4. channelを使って複数のgoroutineを連携させる
channelの真価は、複数のgoroutineを組み合わせて使うときに発揮されます。たとえば、データを生成するgoroutineと、それを処理するgoroutineをつなぐことができます。
package main
import (
"fmt"
"time"
)
func worker(ch chan string) {
for msg := range ch {
fmt.Println("受信:", msg)
time.Sleep(time.Millisecond * 500)
}
}
func main() {
ch := make(chan string)
go worker(ch)
for i := 1; i <= 3; i++ {
ch <- fmt.Sprintf("メッセージ%d", i)
}
close(ch)
}
受信: メッセージ1
受信: メッセージ2
受信: メッセージ3
この例では、worker関数がchannelからメッセージを受け取り、処理をしています。メイン関数では3つのメッセージを順に送信し、close(ch)でchannelを閉じています。range chを使うと、channelが閉じられるまで繰り返しデータを受け取ることができます。
5. channelを閉じるとは?
close()関数を使うと、channelへの送信を終了できます。閉じられたchannelにさらにデータを送ろうとするとエラーになりますが、受信側は「データがもう来ない」と判断して処理を止めることができます。
これは、たとえば「これで全部の荷物を渡し終えましたよ」と伝えるようなものです。受け取る側は、その合図で作業を終えることができます。
6. channelの向きを指定して安全に使う
Goでは、関数の引数で「送信用」「受信用」のchannelを明確に指定することもできます。これにより、意図しない誤操作を防げます。
func send(ch chan<- string) {
ch <- "データ送信"
}
func receive(ch <-chan string) {
fmt.Println(<-ch)
}
func main() {
ch := make(chan string)
go send(ch)
receive(ch)
}
chan<- stringは「送信専用」、<-chan stringは「受信専用」です。これにより、送信専用の関数で誤ってデータを受け取るようなバグを防げます。
7. channelの使いどころ
channelは、次のような場面で特に役立ちます。
- 複数のタスクを同時に動かし、それぞれの結果をまとめる
- Webサーバーで複数のリクエストを並行処理する
- バックグラウンド処理の進捗をメイン処理に通知する
つまり、channelを使うことで「複数のgoroutineが協力して動く」ようなプログラムが書けるのです。これがGo言語の強みでもあります。