Go言語のchannel(チャネル)送受信の基本!goroutineと連携して学ぶ並行処理
生徒
「先生、Go言語で『チャネル(channel)』ってよく聞くんですけど、どう使うんですか?」
先生
「良い質問ですね!Go言語のチャネルは、goroutine(ゴルーチン)同士でデータをやり取りするための仕組みです。たとえば、並行処理で複数の作業を同時に進めながら、結果を受け渡すときにとても便利なんですよ。」
生徒
「なるほど。でも、送信とか受信ってどうやるんですか?」
先生
「それでは、Go言語のチャネル送受信の基本的な書き方を一緒に見ていきましょう!」
1. channel(チャネル)とは?
Go言語(Golang)のchannel(チャネル)は、goroutine間でデータを安全に受け渡すための機能です。複数の処理が同時に動くと、変数の取り合い(競合)が起こることがありますが、チャネルを使えばそれを防ぎながらデータをやり取りできます。
イメージとしては「データを渡すためのパイプ」です。あるgoroutineがチャネルにデータを“送信”し、別のgoroutineがそれを“受信”します。
2. channelの作り方
まず、Go言語ではmake関数を使ってチャネルを作成します。チャネルには、やり取りするデータの型を指定します。
ch := make(chan int)
上記のコードでは、整数(int)型のデータを送受信するためのチャネルを作っています。このチャネルを通して、goroutine同士が整数の値を渡し合うことができます。
3. channelの送信と受信の基本構文
チャネルを使った送受信には、<-(矢印)を使います。この矢印の向きで「送信」か「受信」かが決まります。
- 送信:
ch <- 値(チャネルに値を送る) - 受信:
変数 := <- ch(チャネルから値を受け取る)
たとえば、次のように使います。
ch := make(chan string)
go func() {
ch <- "こんにちは!" // チャネルにデータを送信
}()
msg := <-ch // チャネルからデータを受信
fmt.Println(msg)
こんにちは!
この例では、無名関数(名前を持たない関数)がgoroutineとして同時に動き、文字列「こんにちは!」をチャネルに送信しています。そして、メイン関数側がそのメッセージを受信して表示しています。
4. channelとgoroutineを組み合わせた実例
次は、複数のgoroutineが同時に動き、それぞれがチャネルにデータを送る例を見てみましょう。
package main
import (
"fmt"
"time"
)
func worker(id int, ch chan string) {
time.Sleep(time.Duration(id) * time.Second)
ch <- fmt.Sprintf("Worker %d 完了", id)
}
func main() {
ch := make(chan string)
for i := 1; i <= 3; i++ {
go worker(i, ch)
}
for i := 1; i <= 3; i++ {
msg := <-ch
fmt.Println(msg)
}
}
Worker 1 完了
Worker 2 完了
Worker 3 完了
このプログラムでは、3つのgoroutine(worker関数)が並行して実行され、それぞれが完了メッセージをチャネルへ送信します。メイン関数では、3回受信してすべてのメッセージを受け取ります。
チャネルは、データの受け渡しが終わるまで待機する仕組みを持っているため、goroutine同士のタイミングを自然に合わせることができます。これを「同期的な通信」と呼びます。
5. 受信専用・送信専用channel
Go言語では、安全性を高めるために「送信専用」や「受信専用」のチャネルを定義することもできます。
func sendData(ch chan<- int) {
ch <- 10
}
func receiveData(ch <-chan int) {
fmt.Println(<-ch)
}
ここでは、chan<-は「送信専用」、<-chanは「受信専用」を意味します。関数の引数にこのように指定することで、誤って逆方向に使うミスを防ぐことができます。
6. channelの閉鎖(close)
チャネルを使い終わったら、close関数で閉じることができます。閉じたチャネルにはもう送信できませんが、受信側は残っているデータを読み取ることができます。
ch := make(chan int)
go func() {
for i := 1; i <= 3; i++ {
ch <- i
}
close(ch)
}()
for num := range ch {
fmt.Println(num)
}
1
2
3
rangeを使うことで、チャネルが閉じられるまで自動的に受信を繰り返すことができます。これにより、複数のデータを順番に受け取る処理がシンプルになります。
7. channelを使うメリット
Go言語のチャネルを使う最大のメリットは、「スレッドセーフ(安全に並行処理できる)」という点です。複雑なロック(排他制御)を意識せずに、データの送受信を自然に扱えます。
また、チャネルを活用することで、複数の処理を同時に進めながら、結果を効率よくまとめることが可能になります。サーバー開発やデータ処理など、実際の業務でも非常に役立つ概念です。