WaitGroupの基本的な使い方
Go言語のsync
パッケージには、WaitGroup
という便利な型があります。これは、複数のゴルーチンが終了するのを待つためのものです。
以下に、WaitGroup
の基本的な使い方を示します。
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
}
このコードでは、5つのゴルーチンを起動し、それらがすべて終了するのを待っています。各ゴルーチンは、開始と終了を報告します。
wg.Add(1)
で、WaitGroupのカウンタを1増やします。go worker(i, &wg)
で、新しいゴルーチンを起動します。このゴルーチンは、終了時にwg.Done()
を呼び出して、WaitGroupのカウンタを1減らします。wg.Wait()
は、WaitGroupのカウンタが0になるのを待ちます。つまり、すべてのゴルーチンがDone()
を呼び出すのを待つことになります。
これが、WaitGroup
の基本的な使い方です。これにより、複数のゴルーチンの終了を効率的に待つことができます。次のセクションでは、ポインタを用いたWaitGroup
の操作について説明します。
ポインタを用いたWaitGroupの操作
前のセクションでは、WaitGroup
の基本的な使い方を見てきました。ここでは、ポインタを用いたWaitGroup
の操作について説明します。
Go言語では、関数やメソッドに値を渡すときには値渡し(コピー)が行われます。しかし、WaitGroup
のような同期プリミティブは、複数のゴルーチン間で共有されるため、ポインタを用いて参照渡し(オリジナルの値への参照)を行う必要があります。
以下に、ポインタを用いたWaitGroup
の操作を示します。
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
}
このコードでは、worker
関数にWaitGroup
のポインタを渡しています。これにより、worker
関数内でWaitGroup
のDone
メソッドを呼び出すことができます。
このように、ポインタを用いてWaitGroup
を操作することで、複数のゴルーチン間で同期を取ることができます。次のセクションでは、WaitGroup
の落とし穴と注意点について説明します。
WaitGroupの落とし穴と注意点
WaitGroup
は非常に便利な同期プリミティブですが、正しく使用しないと予期しない結果を招くことがあります。以下に、WaitGroup
の一般的な落とし穴とその対処法について説明します。
-
Add
とDone
の呼び出し順序:Add
メソッドは、新しいゴルーチンを起動する前に呼び出す必要があります。これは、新しいゴルーチンがすぐに終了し、その結果Done
がAdd
より先に呼び出される可能性があるためです。これにより、WaitGroup
のカウンタが負の値になり、ランタイムパニックを引き起こす可能性があります。 -
WaitGroup
の再利用:WaitGroup
は、Wait
メソッドが完了した後に再利用することはできません。再利用すると、WaitGroup
の内部状態が不定になり、予期しない動作を引き起こす可能性があります。 -
Wait
の呼び出し位置:Wait
メソッドは、すべてのゴルーチンが終了するのを確実に待つため、すべてのゴルーチンの起動後に呼び出す必要があります。Wait
を早すぎるタイミングで呼び出すと、すべてのゴルーチンが終了する前にプログラムが終了してしまう可能性があります。
以上が、WaitGroup
の一般的な落とし穴と注意点です。これらを理解し、適切に対処することで、WaitGroup
を安全に使用することができます。次のセクションでは、WaitGroup
とポインタを用いた並列処理の実践例について説明します。
実践:WaitGroupとポインタを用いた並列処理
ここでは、WaitGroup
とポインタを用いて、複数のゴルーチンを効率的に管理する並列処理の実践例を見てみましょう。
以下に、5つのゴルーチンを起動し、それらがすべて終了するのを待つコードを示します。
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers done")
}
このコードでは、main
関数内でWaitGroup
を作成し、5つのゴルーチンを起動しています。各ゴルーチンは、一定時間待機した後に終了します。WaitGroup
のWait
メソッドは、すべてのゴルーチンが終了するのを待ち、その後で”すべてのワーカーが終了しました”と表示します。
このように、WaitGroup
とポインタを用いることで、複数のゴルーチンを効率的に管理し、並列処理を行うことができます。これらの概念を理解し、適切に使用することで、Go言語の並列処理の力を最大限に引き出すことができます。