結論:goroutine(ゴルーチン)とは「関数の前に go を付けるだけで、その処理を別の流れで同時に走らせる仕組み」です。 そして同時に走る処理どうしが安全に値を受け渡す通り道が channel(チャネル)。この2つが Go の並行処理のほぼすべてで、どちらもブラウザ上で実際に動かして確かめられます。
「並行処理」と聞くと難しそうですが、Go は他の言語よりずっと少ない道具で書けるように設計されています。この記事では go キーワードと channel の使い方を、順番に手を動かしながら理解します。
goroutineとは ― go を付けるだけで同時に走る
普通のプログラムは、上から下へ1行ずつ順番に実行されます。これを「逐次(ちくじ)実行」と呼びます。それに対して goroutine は、ある処理を「別の流れ」で同時に走らせる ための単位です。
書き方はとても簡単で、関数呼び出しの前に go を付けるだけです。
go task("A") // task("A") を別の流れで実行(すぐ次の行へ進む)
task("B") // こちらは通常どおり実行
go task("A") と書くと、main は task("A") の完了を待たずにすぐ次の行へ進みます。その結果、A と B の処理が入り混じって進みます。これが並行処理の入り口です。
goroutine の強みは とても軽い こと。OS のスレッドより小さく、数千個作ってもびくともしません。だから Go は「たくさんの仕事を同時にさばくサーバー」の用途で広く使われています。
この goroutine の考え方は、Goコースの「goroutine ― 処理を同時に走らせる」レッスン でコード付きで解説しています(このレッスンは読み物です)。go を付ける/外すで出力の順番がどう変わるかを、次のレッスンから ブラウザでそのまま実行 して確かめられます(無料・登録不要)。
channelの使い方 ― goroutine 間で値を受け渡す
goroutine は別々に走るので、「その処理の結果をどう受け取るか」が次の問題になります。そこで登場するのが channel(チャネル) です。Go には「メモリを共有して通信するな、通信してメモリを共有せよ」という有名な合言葉があり、その「通信」を担うのが channel です。
ch := make(chan string) // string を運ぶチャネルを作る
go func() {
ch <- "計算おわり" // チャネルに値を送る
}()
msg := <-ch // チャネルから値を受け取る(届くまで待つ)
矢印の向きがポイントです。
ch <- "値"… チャネルに値を 送る(矢印がチャネルへ向かう)<-ch… チャネルから値を 受け取る(矢印がチャネルから出る)
そして重要なのが、受け取り側は値が届くまで自動的に待つ こと。この「待ち合わせ」が、そのまま同期(タイミングを合わせること)の役割を果たします。「たぶんもう終わっただろう」と適当に待つ必要がなくなるのです。
このコードは Goコースの「channel ― goroutine 間で値を受け渡す」レッスン で ブラウザ上でそのまま実行 できます。値の送受信を自分で書き換えて、待ち合わせが起きる様子を体感してみてください。
複数の goroutine を待つ ― WaitGroup と select
goroutine が1つなら channel の受信で待てますが、実務では「たくさん起動して、全部終わったら次へ」という場面が普通です。そのための道具が2つあります。
sync.WaitGroup… 起動した goroutine の数を数えておき、全部終わるまで待つ。Addで登録、Doneで完了、Waitで待機。複数の処理をまとめて待つ定番です。→ WaitGroup のレッスン(ブラウザで実行可)select… 複数のチャネルのうち「先に届いたほう」を処理する。並行処理版のswitchと考えると分かりやすいです。タイムアウト(一定時間来なければ諦める)もこれで書きます。→ select のレッスン(ブラウザで実行可)
Goコースの並行処理は、最初の goroutine レッスン(読み物)を除き、channel・WaitGroup・select の回をすべてブラウザ内の実行環境で動かせます。環境構築なしで、go run の代わりに実行ボタンを押すだけです。
つまずきやすいところ
初心者が並行処理で最初にぶつかるのは、次の2つです。
mainが終わると goroutine も打ち切られる。go task()を起動しても、mainがすぐ終わってしまうと、走りかけの goroutine は途中で強制終了され、何も表示されないことがあります。だから channel の受信や WaitGroup で「終わるまで待つ」必要があります。- お互いが待ち続けて止まる=デッドロック。channel は「受け取る相手がいない値を送ろうとする」と、そこで永遠に待ち続けます。Go はこれを検知すると
fatal error: all goroutines are asleep - deadlock!というメッセージで教えてくれます。送信と受信が対になっているかを確認しましょう。
なお、並行処理に限らず Go でよく出るエラーの読み方は Goのエラー逆引き にまとめてあります。エラーメッセージが出て手が止まったら、まずここで検索してみてください。
次に読む
- goroutine ― 処理を同時に走らせる(並行処理の入り口・読み物)
- channel ― goroutine 間で値を受け渡す(ブラウザで実行)
- 複数の goroutine を待つ ― WaitGroup(ブラウザで実行)
- select ― 複数のチャネルを待ち受ける(ブラウザで実行)
- Goそのものが初めてなら はじめての Go ― Hello, World から。全体像は Goコースのトップ を参照 →
- 学ぶ順序に迷ったら Go独学ロードマップ もどうぞ。