Go言語のchanでチャネル(Channel)を使う方法をわかりやすく解説

スポンサーリンク
スポンサーリンク

chanの概要

ゴルーチン間の通信 Goの予約語

chan

概要chanは、Goにおけるゴルーチン間のデータ通信を行うためのチャネルを作成するためのキーワードです。

  • チャネルはゴルーチン同士が安全にデータをやり取りするために使われる。
  • chanを使うことで、明示的なロックなしで並行処理を実現できる。
  • チャネルにはバッファあり(非同期)とバッファなし(同期)の2種類がある。

基本的なchanの使い方

チャネルを使って、ゴルーチン間でデータを送受信する基本的な例です。

package main

import (
    "fmt"
)

func main() {
    ch := make(chan string) // チャネルを作成

    go func() {
        ch <- "Hello, Channel!" // チャネルにデータを送信
    }()

    msg := <-ch // チャネルからデータを受信
    fmt.Println(msg)
}

解説:

  • ch := make(chan string) で、文字列型のチャネルを作成します。
  • ゴルーチン内で ch <- "Hello, Channel!" を実行し、チャネルにデータを送信します。
  • メインゴルーチンで msg := <-ch を実行し、チャネルからデータを受信します。
  • このコードでは、受信するまで送信側がブロックされるため、データの受け渡しが確実に行われます。

実行結果:

Hello, Channel!

バッファなしチャネル(同期チャネル)

バッファなしのチャネルは、送信側と受信側が揃わないと処理が進まないため、同期的にデータをやり取りします。

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int) // バッファなしチャネル

    go func() {
        fmt.Println("送信開始")
        ch <- 42 // 受信側がいないとブロックされる
        fmt.Println("送信完了")
    }()

    fmt.Println("受信:", <-ch)
}

解説:

  • make(chan int) でバッファなしの整数チャネルを作成します。
  • ゴルーチン内で ch <- 42 を実行すると、受信側がデータを受け取るまでブロックされます。
  • 受信側の fmt.Println("受信:", <-ch) が実行されると、送信が完了し、ゴルーチンの処理が続行します。

実行結果:

送信開始
受信: 42
送信完了

バッファ付きチャネル(非同期チャネル)

バッファ付きチャネルは、一定数のデータを送信側が待たずに格納できるため、非同期的にデータをやり取りできます。

package main

import (
    "fmt"
)

func main() {
    ch := make(chan string, 2) // バッファサイズ2のチャネル

    ch <- "メッセージ1"
    ch <- "メッセージ2"

    fmt.Println(<-ch) // 先に送ったメッセージから取り出す
    fmt.Println(<-ch)
}

解説:

  • make(chan string, 2) で、バッファサイズ2のチャネルを作成します。
  • 送信側はバッファにデータを2つまで格納でき、受信側が取り出す前に送信できます。
  • 3つ目のデータを送信しようとすると、バッファが空くまでブロックされます。

実行結果:

メッセージ1
メッセージ2

チャネルのクローズとrangeを使った受信

チャネルをクローズすると、データがなくなったことを受信側が検知できます。

package main

import "fmt"

func main() {
    ch := make(chan int, 3)

    ch <- 10
    ch <- 20
    ch <- 30
    close(ch) // チャネルをクローズ

    for val := range ch {
        fmt.Println(val) // クローズされるまでデータを取得
    }
}

解説:

  • close(ch) を使うと、チャネルがクローズされ、新たなデータを送信できなくなります。
  • range ch を使うと、チャネルがクローズされるまでデータを順番に取り出します。

実行結果:

10
20
30

selectを使った複数のチャネルの待機

selectを使うと、複数のチャネルの送受信を同時に監視できます。

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(2 * time.Second)
        ch1 <- "チャネル1のデータ"
    }()

    go func() {
        time.Sleep(1 * time.Second)
        ch2 <- "チャネル2のデータ"
    }()

    select {
    case msg := <-ch1:
        fmt.Println("受信:", msg)
    case msg := <-ch2:
        fmt.Println("受信:", msg)
    }
}

解説:

  • selectを使うと、どのチャネルが最初にデータを送るかを待ち、受信可能なものから処理を実行します。

実行結果:

受信: チャネル2のデータ

注意事項

  • バッファなしチャネルは送受信が同期: 送信側と受信側が揃わないと進まない。
  • バッファ付きチャネルは非同期: バッファが空くまでデータを送信できる。
  • クローズしたチャネルへの送信はエラー: close()後に送信するとパニックが発生。

よくある質問

Q: チャネルをクローズする必要はありますか?
A: 受信側がデータの終了を知る必要がある場合にクローズします。ただし、送信側が存在しなくなる場合は、明示的にクローズしなくてもガベージコレクションの対象になります。
Q: チャネルを二重にクローズするとどうなりますか?
A: ランタイムエラー(panic)が発生します。
Q: チャネルがクローズされたかどうかを確認する方法は?
A: 受信時に2つ目の戻り値を使います。val, ok := <-chok == false ならチャネルがクローズされています。
Q: バッファサイズを大きくするとどんなメリットがありますか?
A: 送信側がブロックされるのを防ぐことができますが、過度に大きくするとメモリを圧迫する可能性があります。
Q: selectとチャネルを組み合わせるメリットは?
A: 複数のチャネルを同時に待機し、最初に受信可能になったものを処理できるため、効率的な非同期処理が可能になります。

まとめ

  • chanはゴルーチン間のデータ通信を行うための機能。
  • バッファなしチャネルは同期的、バッファ付きチャネルは非同期的にデータを送受信できる。
  • クローズされたチャネルをrangeで処理すると、安全にデータを取り出せる。
  • selectを使うと、複数のチャネルの受信を待機できる。