🏃

[Go]Goの並列処理入門:Goroutineとsync.WaitGroupを理解しよう!

2025/02/17に公開

はじめに

Go(Golang)は並行処理が得意なプログラミング言語であり、その強力な特徴の一つにGoroutineがあります。
さらに、複数のGoroutineを管理し、処理が完了するまで待機するためのsync.WaitGroupという仕組みも提供されています。

少し自分もわかりにくい概念があったので知識の整理も兼ねて、Goroutineとsync.WaitGroupを初心者向けにわかりやすく解説していこうと思います。


Goroutineとは?

Goroutine(ゴルーチン)は、Goにおける並行処理の基本単位です。スレッドのように動作しますが、非常に軽量であり、大量のGoroutineを作成してもシステムのリソースを圧迫しません。

使い方

Goroutineは、go キーワードを関数の前に付けるだけで簡単に作成できます。

Goroutineの基本的なコード

package main

import (
    "fmt"
    "time"
)

func sayHello() {
    fmt.Println("Hello from Goroutine!")
}

func main() {
    go sayHello() // Goroutineを起動
    
    fmt.Println("Hello from main!")
    
    time.Sleep(time.Second) // Goroutineの実行を待つためにスリープ
}

実行結果

Hello from main!
Hello from Goroutine!

Goroutineは並行して実行されるため、main() 関数が終了すると、他のGoroutineも終了してしまいます。
そのため、time.Sleep() を使って少し待機することで、Goroutineの動作を確認できます(実際の開発ではsync.WaitGroupを使う方が推奨されます。若干処理速度がタイミングによって変わることがありました)


sync.WaitGroupとは?

Goroutineを複数起動した場合、それらの処理がすべて終了するまで待機したいことがあります。そのために使うのがsync.WaitGroupです。

sync.WaitGroupの使い方

  1. Add(n) を呼び出して、待つGoroutineの数を指定する
  2. 各Goroutine内で Done() を呼び出す
  3. Wait() を呼び出すことで、すべてのGoroutineが終了するまで待機する

sync.WaitGroupを使ったサンプルコード

package main

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

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 処理が完了したらカウントを減らす
    fmt.Printf("Worker %d: started\n", id)
    time.Sleep(time.Second) // 擬似的な重い処理
    fmt.Printf("Worker %d: finished\n", id)
}

func main() {
    var wg sync.WaitGroup
    
    for i := 1; i <= 3; i++ {
        wg.Add(1) // 待つGoroutineの数を増やす
        go worker(i, &wg)
    }
    
    wg.Wait() // すべてのGoroutineが完了するまで待つ
    fmt.Println("All workers are done!")
}

実行結果

Worker 1: started
Worker 2: started
Worker 3: started
Worker 1: finished
Worker 2: finished
Worker 3: finished
All workers are done!

Goroutineは並行して動作するため、実行順は毎回変わることがあります。


Goroutineとsync.WaitGroupの活用例

複数のウェブサイトからデータを取得する場合を考えてみましょう。Goroutineを使うことで高速に処理できます。

ウェブサイトを並行してリクエストするサンプルコード

package main

import (
    "fmt"
    "net/http"
    "sync"
)

func fetchURL(url string, wg *sync.WaitGroup) {
    defer wg.Done()
    
    resp, err := http.Get(url)
    if err != nil {
        fmt.Println("Error fetching:", url, err)
        return
    }
    fmt.Println("Fetched:", url, "Status:", resp.Status)
}

func main() {
    var wg sync.WaitGroup
    urls := []string{
        "https://golang.org",
        "https://example.com",
        "https://github.com",
    }
    
    for _, url := range urls {
        wg.Add(1)
        go fetchURL(url, &wg)
    }
    
    wg.Wait()
    fmt.Println("All requests completed!")
}

実行結果(例)

Fetched: https://golang.org Status: 200 OK
Fetched: https://example.com Status: 200 OK
Fetched: https://github.com Status: 200 OK
All requests completed!

このように、Goroutineを使うことで複数のHTTPリクエストを並列に実行でき、処理時間を大幅に短縮できます。


まとめ

概念 説明
Goroutine 軽量な並行処理の単位。go キーワードで簡単に作成可能
sync.WaitGroup 複数のGoroutineの終了を待機する仕組み
活用例 複数のウェブリクエストや並列処理のタスクを高速に処理

Goroutineを使いこなすことができればできるタスクの幅も広がるな〜と感じました。
マルチプロセスは考え方に馴染むまで時間がかかる印象があるけど頑張ってマスターしたい...

Discussion