Caution

お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。

sync.WaitGroup

『sync.WaitGroup』はGoの並行処理でgoroutineの完了を待つための同期プリミティブです。カウンターを使ってすべてのgoroutineが終了するまでメインの処理をブロックします。

構文
import "sync"

var wg sync.WaitGroup

// カウンターをN増やします(goroutine起動前に呼びます)。
wg.Add(N)

// カウンターを1減らします(goroutine終了時にdeferで呼ぶのが定石)。
wg.Done()

// カウンターが0になるまでブロックします。
wg.Wait()

// 典型的なパターン
for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(i int) {
        defer wg.Done() // 関数終了時に必ず呼ばれます。
        // goroutineの処理
    }(i)
}
wg.Wait() // すべてのgoroutineが完了するまで待ちます。
メソッド一覧
メソッド概要
Add(delta int)カウンターにdelta(正または負)を加算します。goroutine起動前に呼びます。
Done()カウンターを1減らします。Add(-1)と同等です。goroutine終了時に呼びます。
Wait()カウンターが0になるまで呼び出しをブロックします。
サンプルコード
package main

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

// データを並行して処理する関数です。
func processItem(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 関数終了時に必ずDone()を呼びます。
    fmt.Printf("アイテム%d: 処理開始\n", id)
    time.Sleep(time.Duration(id*50) * time.Millisecond) // 処理時間のシミュレーション
    fmt.Printf("アイテム%d: 処理完了\n", id)
}

func main() {
    var wg sync.WaitGroup

    // 5つのgoroutineを起動します。
    for i := 1; i <= 5; i++ {
        wg.Add(1) // goroutine起動前にカウンターを増やします。
        go processItem(i, &wg)
    }

    fmt.Println("すべてのgoroutineの完了を待っています...")
    wg.Wait() // すべてのgoroutineがDone()を呼ぶまでブロックします。
    fmt.Println("すべての処理が完了しました!")

    fmt.Println()

    // 結果を安全に収集するパターン(mutexと組み合わせます)
    var (
        wg2    sync.WaitGroup
        mu     sync.Mutex
        results []int
    )

    for i := 0; i < 5; i++ {
        wg2.Add(1)
        go func(n int) {
            defer wg2.Done()
            result := n * n
            mu.Lock()
            results = append(results, result) // mutexで排他アクセスします。
            mu.Unlock()
        }(i)
    }

    wg2.Wait()
    fmt.Println("二乗の結果(順序不定):", results)
}
概要

『sync.WaitGroup』はgoroutineの完了待ちに最もよく使われる同期プリミティブです。WaitGroupはコピーで渡さず、必ずポインタで渡すようにしてください。また、『Add()』はgoroutineを起動する前に呼ぶ必要があります。goroutine内で『Add()』を呼ぶと競合状態が発生する可能性があります。

WaitGroupは値ではなくポインタ(*sync.WaitGroup)で渡す必要があります。値でコピーすると内部状態も一緒にコピーされ、正しく同期できなくなります。また、Done()をAdd()より多く呼ぶとカウンターが負になりpanicが発生します。

goroutineの基本は『goroutine』、共有データへの排他アクセスには『sync.Mutex / RWMutex』を参照してください。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。