はじめに
Go言語では、非同期のタスクを実行するための手段としてゴルーチンを利用することが一般的です。
しかしながら、その利用には注意が必要です。今回はその一つ、main
関数が終了すると共にゴルーチンも終了してしまう問題とその解決策について記載します。
ゴルーチンとmain
関数の関係性
まずは以下のゴルーチンが含まれたコードを見てみましょう。
package main
import (
"fmt"
)
func sayHello() {
fmt.Println("Hello")
}
func sayWorld() {
fmt.Println("World")
}
func main() {
go sayHello()
go sayWorld()
}
このコードでは、main
関数がsayHello
とsayWorld
のゴルーチンを起動します。
しかし、ここには一つ問題があります。それは、main
関数が終了すると全てのゴルーチンも終了するというGoの特性上、これらのゴルーチンが終了する前にmain
関数が終了してしまい、ゴルーチンが完全に実行されない可能性があるという点です。
暫定的な解決策:time.Sleep
この問題を解決する一つの方法として、main
関数の終了を遅らせるtime.Sleep
関数を使用することが考えられます。
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("Hello")
}
func sayWorld() {
fmt.Println("World")
}
func main() {
go sayHello()
go sayWorld()
// Wait for the goroutines to finish
time.Sleep(time.Second)
}
しかしこの解決策は一時的なものであり、正確なゴルーチンの終了タイミングを保証するものではないため、実際のプログラムでは推奨されません。
正確なゴルーチンの終了管理:sync.WaitGroup
ゴルーチンの終了を管理する正しい方法は、チャネルやsync.WaitGroup
のような同期プリミティブを使用することです。特に、sync.WaitGroup
は、複数のゴルーチンが終了するのを待つための簡便な方法として提供されています。
具体的には、sync.WaitGroup
はカウンタを提供します。このカウンタは、Add
メソッドで増やし、Done
メソッドで減らすことができます。そして、Wait
メソッドはカウンタが0になるまで待ち続けます。
以下にその使用例を示します:
package main
import (
"fmt"
"sync"
)
func sayHello(wg *sync.WaitGroup) {
fmt.Println("Hello")
wg.Done() // Decrease counter
}
func sayWorld(wg *sync.WaitGroup) {
fmt.Println("World")
wg.Done() // Decrease counter
}
func main() {
var wg sync.WaitGroup
wg.Add(2) // Increase counter
go sayHello(&wg)
go sayWorld(&wg)
wg.Wait() // Wait for counter to be 0
}
このコードでは、sync.WaitGroup
を用いてsayHello
とsayWorld
のゴルーチンが確実に終了することを保証しています。main
関数はゴルーチンの起動前にカウンタを2に設定し、各ゴルーチンが終了した時点でカウンタを減らします。最後に、main
関数は全てのゴルーチンが終了する(=カウンタが0になる)のを待ちます。
これにより、main
関数はsayHello
とsayWorld
のゴルーチンが確実に終了するのを待つことができます。特に、ゴルーチンが共有リソース(例えば、ファイルやデータベース接続)のクリーンアップを必要とする場合など、ゴルーチンの終了を待つ必要がある際に有用です。