Goの並行処理|goroutineとchannelを基本から解説

プログラミング言語

Go言語の魅力の1つが、軽量で扱いやすい並行処理機能です。goroutineとchannelの組み合わせで、シンプルかつ強力な並行プログラムを書けます。本記事では、Goの並行処理の基本を解説します。

goroutineとは

goroutineは「軽量スレッド」で、関数呼び出しの前にgoを付けるだけで並行実行できます。1関数あたり数KBで動くため、数万本の同時実行も現実的です。

func say(msg string) {
    fmt.Println(msg)
}

func main() {
    go say("hello")  // 別goroutineで実行
    say("world")
    time.Sleep(100 * time.Millisecond)
}

channelとは

channelはgoroutine間で値をやり取りする「型付きパイプ」です。共有メモリではなく「メッセージで通信する」のがGoの基本思想です。

func producer(ch chan<- int) {
    for i := 0; i < 5; i++ {
        ch <- i  // 送信
    }
    close(ch)
}

func main() {
    ch := make(chan int)
    go producer(ch)
    for v := range ch {  // 受信
        fmt.Println(v)
    }
}

バッファ付きchannel

ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
// 4つ目はブロックする

select文

複数のchannelに対する操作を多重化します。タイムアウトやキャンセル処理にも便利です。

select {
case v := <-ch1:
    fmt.Println("ch1:", v)
case v := <-ch2:
    fmt.Println("ch2:", v)
case <-time.After(1 * time.Second):
    fmt.Println("timeout")
}

sync.WaitGroupで待ち合わせ

var wg sync.WaitGroup
for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(n int) {
        defer wg.Done()
        fmt.Println(n)
    }(i)
}
wg.Wait()

contextでキャンセル制御

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()

go func() {
    select {
    case <-ctx.Done():
        fmt.Println("canceled:", ctx.Err())
    }
}()

よくある落とし穴

  • 共有変数への並行書き込み → mutexまたはchannel経由に
  • channelのcloseは送信側が行う
  • goroutineリーク(終了条件が無いgoroutine)に注意
  • for-loop内のクロージャ変数キャプチャ(Go 1.22以降は改善)

まとめ

Goの並行処理はgoroutine + channel + selectの3点セットで多くの問題を解決できます。共有メモリより「通信で同期する」発想が、Goらしい並行プログラムを書くコツです。慣れれば数行で堅牢な並行処理が書けます。