GoのWaitGroupを理解する

2023/05/27に公開

WaitGroup

goルーチンを使っていると、すべてのルーチンが完了するまで待機したい場合があるかと思います。
そういう時はsyncパッケージのWaitGroupを使うと便利です。

使い方

Addメソッド,Doneメソッド,Waitメソッドがあり、waitgroupに追加するためにまずAddを行います。Addの引数で渡した数だけ、Done()を呼び出すまで、Wait()が処理をブロックしてくれます。

var wg sync.WaitGroup

wg.Add(1)
go func() {
	/*何らかの処理*/
	wg.Done()
}()
wg.Add(1)
go func() {
	/*何らかの処理*/
	wg.Done()
}()

wg.Wait()
fmt.Println("タスクがすべて完了")

試してみる

実際に挙動を見てみましょう。まずはWaitGroupを使わなかった場合です。

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("開始")

	go func() {
		/*5秒待機*/
		time.Sleep(5 * time.Second)
		fmt.Println("end timer1")
	}()

	go func() {
		/*5秒待機*/
		time.Sleep(5 * time.Second)
		fmt.Println("end timer2")
	}()

	fmt.Println("終了")
}

//実行結果
開始
終了

上記の例は、time.Sleepを使用して5秒間待機するgoルーチンを2つ実行していますが、当然終了を待っていないので、fmt.Println("終了")が呼ばれてプログラムが終了します。

では、WaitGroupを使ってみます。

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	fmt.Println("開始")
	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		/*5秒待機*/
		time.Sleep(5 * time.Second)
		fmt.Println("end timer1")
		wg.Done()
	}()

	wg.Add(1)
	go func() {
		/*5秒待機*/
		time.Sleep(5 * time.Second)
		fmt.Println("end timer2")
		wg.Done()
	}()

	wg.Wait()
	fmt.Println("終了")
}
//実行結果
開始
end timer1
end timer2
終了

プログラムを実行すると、開始が表示されて5秒間待機されてから終了が出力されてプログラムが終了されるようになりました!!

以下のようにfor文で回しても使えます。

var wg.WaitGroup
for i := 0; i < 10 ; i++ {
	wg.Add(1)
	go func() {
		/* do something */
		wg.Done()
	}()
}

wg.Wait()

使いどころ

今回はtime.Sleepで挙動を確認しましたが、goルーチンを使って、複数のAPIをたたいたり、複数ページのスクレイピングをしたり、それぞれのgoルーチンの終了タイミングが異なるときなどに便利です。

参考

https://pkg.go.dev/sync#WaitGroup

最後に

WaitGroupは便利なので使っていきましょう。
次は、traceパッケージやsync.Mutexについて書こうかなと思います。

Discussion