Go言語の無名goroutineを使って並行処理を簡単に試そう
生徒
「先生、goroutineには名前のない関数をそのまま並行で動かす方法があるって聞きました。無名関数って何ですか?」
先生
「いい質問だよ。無名関数は名前を持たないその場で書ける関数で、Goではgoの前につけるだけで簡単に並行処理が作れます。まずはイメージから説明しますね。」
生徒
「イメージだとどんな感じですか?」
先生
「台所でお皿を洗いながらご飯を炊くようなものです。どちらも同時に進めたい作業を、無名goroutineならコードを短く書いて同時に動かせます。」
1. 無名goroutineとは?
無名goroutineは、関数に名前を付けずにその場で定義した関数を、goキーワードで別の実行単位として走らせる方法です。短い処理や一時的な並行処理を試すときに便利です。
用語の補足:goroutine(ゴルーチン)はGoの軽量な並行処理単位、無名関数は名前を持たないその場で定義する関数です。
2. 基本的な書き方
無名goroutineは、無名関数の前にgoをつけるだけで作れます。とてもシンプルです。
package main
import (
"fmt"
"time"
)
func main() {
go func() {
fmt.Println("無名goroutine: はじめまして")
}()
fmt.Println("メイン関数: こちらが先に出る場合もある")
time.Sleep(100 * time.Millisecond) // goroutineが動く時間を確保
}
ポイントは、メイン関数がすぐに終了するとgoroutineも止まってしまうことです。短いテストではtime.Sleepで待つことがありますが、本番ではsync.WaitGroupなどを使って確実に待ち合わせします。
3. 無名goroutineで引数や変数を使うときの注意
無名関数内で外側の変数を参照すると、ループや並行処理で意図しない値が使われることがあります。特にループ内でgoroutineを立てるときは、変数のコピーを渡すと安全です。
for i := 0; i < 3; i++ {
go func(n int) {
fmt.Println("値:", n)
}(i) // iを引数として渡してコピーする
}
time.Sleep(500 * time.Millisecond)
このように引数で値を渡すと、各goroutineは期待どおりのiの値を受け取れます。もし引数にしないで直接外側のiを参照すると、すべてのgoroutineが最終値を見る場合があります。
4. 実用的な例:並列で複数の処理を試す
無名goroutineは、複数の短いタスクを同時に試したいときに有用です。次の例は、3つの短い処理を並行で行い、結果をチャネルで受け取る例です。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
for i := 1; i <= 3; i++ {
idx := i
go func() {
time.Sleep(time.Duration(100*idx) * time.Millisecond)
ch <- fmt.Sprintf("タスク%d 完了", idx)
}()
}
for i := 1; i <= 3; i++ {
fmt.Println(<-ch)
}
}
このプログラムでは、無名goroutineがそれぞれ遅延後にチャネルへメッセージを送ります。メイン関数はチャネルから順に受信して出力します。
5. WaitGroupを使った安全な待ち合わせ
実際のプログラムでは、time.Sleepに頼るのは良くありません。代わりにsync.WaitGroupを使うと、全てのgoroutineの終了を確実に待てます。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
idx := i
go func() {
defer wg.Done()
fmt.Println("処理中:", idx)
}()
}
wg.Wait()
fmt.Println("すべてのgoroutineが終了しました")
}
この例では、無名goroutine内でwg.Done()を呼ぶことでメインがwg.Wait()で待機でき、確実に全ての処理が終わるまで次に進みません。
6. 無名goroutineを試すときのポイント
- 短いテストや使い捨ての処理に便利。
- ループ内で外側の変数を参照するときは値をコピーする(引数で渡す)。
time.Sleepで待つのは簡単だが本番向けではない。sync.WaitGroupを使おう。- goroutineは軽量だが、無制限に作るとメモリやスケジューリングに負荷がかかるので注意する。
無名goroutineは並行処理を手早く試すのに最適です。まずは短いサンプルを動かして、並行実行の出力の仕方やタイミングの変化を観察してみましょう。