sync.WaitGroupでGoの並行処理を確実に終了する!

Go言語
この記事は約7分で読めます。
Go初学者
Go初学者

Goroutineでの並行処理を確実に終了する方法はあるのかな?

この記事ではGoの並行処理を確実に終了する方法を理解できます!
  • Go言語の並行処理を確実に終了する sync.WaitGroup について
  • 実際にsync.WaitGroup を使用したプログラムで並行処理の動きを確認

当ブログでGoroutineによる並行処理のプログラムを紹介し、実際に動きを確認していただきました。

しかし、このプログラムには並行処理を実装する上では不完全なものなのです。

今回は、Go言語の並行処理を確実に終了するためのプログラムをお伝えしていきます。

もりぴ
この記事を書いた人

XHTML1.0時代にHTML&CSSを勉強した経験あり。無趣味だった私が2020年5月からプログラミング学習を開始し現在も挫折せずに趣味で学習を楽しんでいる51歳。プログラミングの楽しさをブログを通してお伝えしていきます。

もりぴをフォローする

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とは?

sync.WaitGroupは、複数のGoroutineを並行処理が終了するまで待ってもらうための値

先程のプログラムで例えると、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の処理を全て完了することができました。

もりぴ
もりぴ

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

gorutine並行処理イメージ

【まとめ】sync.WaitGroupでGoの並行処理を確実に終了する

sync.WaitGroup でGoの並行処理を確実に終了するプログラムをお伝えしていきました。

まとめとして、今回使用したプログラムを用いて流れを確認していきます。

sync.WaitGroupのプログラムコード記述の流れを確認する

sync.WaitGroupのプログラムコード記述の流れ
  • STEP1
    変数宣言する

    var swg sync.WaitGroupsync.WaitGroup の値を代入する

  • STEP2
    goroutineの処理が1つあると定義

    swg.Add(1) と定義
    注意点として、go文の前に必ず記述する

  • STEP3
    go文の関数の第2引数に変数のポインタを渡す

    go loop("Hello", &swg)

  • STEP4
    goroutineの処理を待つ

    swg.Wait()

  • STEP5
    goroutineで処理する関数の第2引数にポインタを指定する

    func loop(s string, swg *sync.WaitGroup){}

  • STEP6
    goroutineで処理する関数の終了を知らせる

    defer swg.Done()
    処理の終了を確実に知らせるため defer で記述しておく

sync.WaitGroupのプログラム処理の流れを確認する

sync.WaitGroupのプログラム処理の流れ
  • STEP1
    gorutineの処理を開始して終了を知らせる

    goroutineの処理が終わると swg.Done() が実行される

  • STEP2
    goroutineの処理終了を確認する

    swg.Done() が実行されると swg.Add(1) のカウントがひとつ減る

  • STEP3
    goroutineの処理が終了するまでプログラム終了を待つ

    swg.Add(1) の値が 0 になるまで swg.Wait() がプログラム終了を待つ

  • STEP4
    プログラム終了

    swg.Add()0 になったのを確認してプログラム終了

以上です。

ちょっと難しかったかもしれませんが、実際にエディタでコードを書いて動きを確認してみましょう。

そして、今回はGoroutineの処理が1つでしたが、これを複数にして自力でプログラムを完成させましょう。

sync.WaitGroup の理解がより深まりますよ。

comment

タイトルとURLをコピーしました