
Goroutineでの並行処理を確実に終了する方法はあるのかな?
当ブログでGoroutineによる並行処理のプログラムを紹介し、実際に動きを確認していただきました。
しかし、このプログラムには並行処理を実装する上では不完全なものなのです。
今回は、Go言語の並行処理を確実に終了するためのプログラムをお伝えしていきます。
Goroutine入門でのプログラムの問題点
参考記事のプログラムを再確認します。
// Goroutine入門でのプログラム再確認
package main
import (
"fmt"
"time" // 時間の測定と表示機能
)
func loop(s string) {
for i := 0; i < 5; i++ {
// 100ミリ秒ごとに実行
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func subLoop(s string) {
for i := 0; i < 5; i++ {
// 150ミリ秒ごとに実行
time.Sleep(150 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
// goでひとつの関数を並行処理
go loop("Hello")
subLoop("World")
}
// 出力結果
$ go run main.go
Hello
World
Hello
Hello
World
Hello
World
Hello
World
World

でも、このプログラムの time.Sleep()
コメントアウトすると正常に並行処理がされないんです…
// Goroutine入門でのプログラム再確認
package main
import (
"fmt"
)
func loop(s string) {
for i := 0; i < 5; i++ {
// time.Sleep()をコメントアウト
// time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func subLoop(s string) {
for i := 0; i < 5; i++ {
// time.Sleep()をコメントアウト
// time.Sleep(150 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
// goでひとつの関数を並行処理
go loop("Hello")
subLoop("World")
}
// 出力結果
$ go run main.go
World
World
World
World
World
go loop("Hello")
の処理が実行される前に、main()
関数の subLoop("World")
の処理が終了した時点でプログラムは終了します。
time.Sleep()
を使用していたのは、あくまでも並行処理を実現させるためにプログラムを遅延させるためだったのです。
では、並行処理がきちんとされるようにするにはどうしたら良いでしょうか?

main()
関数で処理するプログラムが、Goroutineで設定した関数の処理が終了するまで待ってもらえばいいのです!
sync.WaitGroupで並行処理を確実に終了する
それでは、Goruotineで設定した処理をきちんと終了まで待ってくれるプログラムを確認していきましょう。
sync.WaitGroupとは?
先程のプログラムで例えると、go loop("Hello")
の処理が終わるまで、プログラム終了を待ってもらための値です。
sync.WaitGroupは、Goroutineのために提供されている構造体です。
sync.WaitGroupで並行処理を確実に終了するプログラム
今回は time.Sleep()
は使用しません。
// sync.WaitGroupで並行処理を確実に終了するプログラム
package main
import (
"fmt"
"sync" // sync.WaitGroupを使用するため
)
// 引数にswgを追加
func loop(s string, swg *sync.WaitGroup) {
// 処理の終了確認
defer swg.Done()
for i := 0; i < 5; i++ {
fmt.Println(s)
}
}
func sub2Loop(s string) {
for i := 0; i < 5; i++ {
fmt.Println(s)
}
}
func main() {
// sync.WaitGroupの値を代入するため変数宣言
var swg sync.WaitGroup
// goroutineの処理が1つあると定義
swg.Add(1)
// 第2引数にswgのポインタを渡す
go loop("Hello", &swg)
sub2Loop("World")
// wg.Done()の処理が終わるまで待つ
swg.Wait()
}
// 出力結果
$ go run main.go
World
World
World
World
World
Hello
Hello
Hello
Hello
Hello
time.Sleep()
を使用せずにGoroutineの処理を全て完了することができました。

このプログラムを私なりにイメージ化したのが、これです…

【まとめ】sync.WaitGroupでGoの並行処理を確実に終了する
sync.WaitGroup
でGoの並行処理を確実に終了するプログラムをお伝えしていきました。
まとめとして、今回使用したプログラムを用いて流れを確認していきます。
sync.WaitGroupのプログラムコード記述の流れを確認する
- STEP1変数宣言する
var swg sync.WaitGroup
でsync.WaitGroup
の値を代入する - STEP2goroutineの処理が1つあると定義
swg.Add(1)
と定義
注意点として、go文の前に必ず記述する - STEP3go文の関数の第2引数に変数のポインタを渡す
go loop("Hello", &swg)
- STEP4goroutineの処理を待つ
swg.Wait()
- STEP5goroutineで処理する関数の第2引数にポインタを指定する
func loop(s string, swg *sync.WaitGroup){}
- STEP6goroutineで処理する関数の終了を知らせる
defer swg.Done()
処理の終了を確実に知らせるため defer で記述しておく
sync.WaitGroupのプログラム処理の流れを確認する
- STEP1gorutineの処理を開始して終了を知らせる
goroutineの処理が終わると
swg.Done()
が実行される - STEP2goroutineの処理終了を確認する
swg.Done()
が実行されるとswg.Add(1)
のカウントがひとつ減る - STEP3goroutineの処理が終了するまでプログラム終了を待つ
swg.Add(1)
の値が0
になるまでswg.Wait()
がプログラム終了を待つ - STEP4プログラム終了
swg.Add()
が0
になったのを確認してプログラム終了
以上です。
ちょっと難しかったかもしれませんが、実際にエディタでコードを書いて動きを確認してみましょう。
そして、今回はGoroutineの処理が1つでしたが、これを複数にして自力でプログラムを完成させましょう。
sync.WaitGroup
の理解がより深まりますよ。
comment