Menu
Coddy logo textTech

sync.WaitGroup

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 64/107。

「Goroutines Basics」レッスンでは、ゴルーチンが終了するまで待つために time.Sleep を使用しました。これは脆弱なアプローチです。sync.WaitGroup は、goroutines の集合が作業を complete するまで待つための適切な方法を提供します。

WaitGroup は counter のように機能します。goroutine を開始する before に increment し、goroutine が完了したときに decrement し、counter が 0 に達するまで block します:

func main() {
    var wg sync.WaitGroup
    
    for i := 1; i <= 3; i++ {
        wg.Add(1)  // カウンターをインクリメント
        go func(id int) {
            defer wg.Done()  // 完了時にデクリメント
            fmt.Printf("Worker %d finished\n", id)
        }(i)
    }
    
    wg.Wait()  // カウンターが0になるまでブロック
    fmt.Println("All workers complete")
}

3つの主要なメソッドは、counter を n 増加させる Add(n)、counter を1減少させる(Add(-1)と同等)Done()、そして counter がゼロになるまでブロックする Wait() です。defer wg.Done() を使用すると、goroutine がパニックになった場合でも counter が減少します。

よくある間違いは、Add を goroutine の中で呼び出し、その前に呼び出さないことです。これにより、すべての goroutine が登録される前に Wait が戻る可能性がある競合状態が発生します。

// 間違い - レースコンディション
go func() {
    wg.Add(1)  // Wait() の後に実行される可能性がある
    defer wg.Done()
}()

// 正しい - 起動前に Add
wg.Add(1)
go func() {
    defer wg.Done()
}()

WaitGroupsは、concurrentな操作をcoordinationするためにstructに埋め込まれることが多く、Goでスレッドセーフな型を構築するうえで不可欠です。

challenge icon

チャレンジ

簡単

sync.WaitGroupを使用して、複数の同時実行される file downloads を調整する download manager を構築しましょう。manager は、任意の sleep timer に頼らず、すべての downloads が complete したタイミングを追跡します。

コードを2つのファイルに分けて整理します。

  • downloader.go: download の coordination logic を定義します。

    ID(int)、Filename(string)、Size(KBを表すint)フィールドを持つDownload struct を Create します。

    DownloadProcess(wg *sync.WaitGroup) method を Implement します。この method は、file size に基づく Duration(Size * 10 milliseconds を使用)だけ sleep することで downloading をシミュレートします。download が complete したら、Downloaded: [Filename] ([Size]KB) を print します。WaitGroup の counter が正しく decrement されるように、defer wg.Done() を使用します。

    すべての downloads を goroutines を使用して concurrently に launch し、すべての downloads が complete するまで待機する function StartDownloads(downloads []Download) int を Create します。各 goroutine を launch する前に wg.Add(1) を call し、goroutine の内部では call しないようにしてください。すべての downloads の Size を合計した total size を return します。

  • main.go: download information を読み取り、concurrent downloads を調整します。

    downloads の数を読み取り、続いて各 download の ID、filename、size を読み取ります。downloads を Create し、StartDownloads に渡します。すべての downloads が complete した後、All downloads complete: [total]KB total を print します。

次の inputs が提供されます。

  • Line 1: downloads の数(integer)
  • Following lines: 各 download について3行 — ID(integer)、filename(string)、size(KB単位のinteger)

例えば、次の入力が与えられた場合:

3
1
report.pdf
20
2
image.png
5
3
data.csv
10

output には downloads が complete する順序(sleep time が短いため、小さい file が先に終了します)が表示され、その後に summary が続きます。

Downloaded: image.png (5KB)
Downloaded: data.csv (10KB)
Downloaded: report.pdf (20KB)
All downloads complete: 35KB total

time.Sleep を使用する場合との key difference は、wg.Wait() が、すべての goroutines が Done() で completion を signal するまで block することです。これにより、各 download にかかる時間に関係なく、正確な synchronization が得られます。

自分で試してみよう

package main

import (
	"fmt"
)

func main() {
	// ダウンロード数を読み取る
	var n int
	fmt.Scanln(&n)

	// ダウンロード情報を読み取る
	downloads := make([]Download, n)
	for i := 0; i < n; i++ {
		var id int
		var filename string
		var size int
		fmt.Scanln(&id)
		fmt.Scanln(&filename)
		fmt.Scanln(&size)
		
		downloads[i] = Download{
			ID:       id,
			Filename: filename,
			Size:     size,
		}
	}

	// TODO: downloads スライスで StartDownloads を呼び出す
	// TODO: Print the summary: "All downloads complete: [total]KB total"
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Goオンラインコンパイラ