Go言語:WaitGroupとポインタの理解と活用

By quonta 4月 12, 2024

WaitGroupの基本的な使い方

Go言語のsyncパッケージには、WaitGroupという便利な型があります。これは、複数のゴルーチンが終了するのを待つためのものです。

以下に、WaitGroupの基本的な使い方を示します。

package main

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

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()

    fmt.Printf("Worker %d starting\n", id)

    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    wg.Wait()
}

このコードでは、5つのゴルーチンを起動し、それらがすべて終了するのを待っています。各ゴルーチンは、開始と終了を報告します。

  • wg.Add(1)で、WaitGroupのカウンタを1増やします。
  • go worker(i, &wg)で、新しいゴルーチンを起動します。このゴルーチンは、終了時にwg.Done()を呼び出して、WaitGroupのカウンタを1減らします。
  • wg.Wait()は、WaitGroupのカウンタが0になるのを待ちます。つまり、すべてのゴルーチンがDone()を呼び出すのを待つことになります。

これが、WaitGroupの基本的な使い方です。これにより、複数のゴルーチンの終了を効率的に待つことができます。次のセクションでは、ポインタを用いたWaitGroupの操作について説明します。

ポインタを用いたWaitGroupの操作

前のセクションでは、WaitGroupの基本的な使い方を見てきました。ここでは、ポインタを用いたWaitGroupの操作について説明します。

Go言語では、関数やメソッドに値を渡すときには値渡し(コピー)が行われます。しかし、WaitGroupのような同期プリミティブは、複数のゴルーチン間で共有されるため、ポインタを用いて参照渡し(オリジナルの値への参照)を行う必要があります。

以下に、ポインタを用いたWaitGroupの操作を示します。

package main

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

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()

    fmt.Printf("Worker %d starting\n", id)

    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    wg.Wait()
}

このコードでは、worker関数にWaitGroupのポインタを渡しています。これにより、worker関数内でWaitGroupDoneメソッドを呼び出すことができます。

このように、ポインタを用いてWaitGroupを操作することで、複数のゴルーチン間で同期を取ることができます。次のセクションでは、WaitGroupの落とし穴と注意点について説明します。

WaitGroupの落とし穴と注意点

WaitGroupは非常に便利な同期プリミティブですが、正しく使用しないと予期しない結果を招くことがあります。以下に、WaitGroupの一般的な落とし穴とその対処法について説明します。

  1. AddDoneの呼び出し順序: Addメソッドは、新しいゴルーチンを起動する前に呼び出す必要があります。これは、新しいゴルーチンがすぐに終了し、その結果DoneAddより先に呼び出される可能性があるためです。これにより、WaitGroupのカウンタが負の値になり、ランタイムパニックを引き起こす可能性があります。

  2. WaitGroupの再利用: WaitGroupは、Waitメソッドが完了した後に再利用することはできません。再利用すると、WaitGroupの内部状態が不定になり、予期しない動作を引き起こす可能性があります。

  3. Waitの呼び出し位置: Waitメソッドは、すべてのゴルーチンが終了するのを確実に待つため、すべてのゴルーチンの起動後に呼び出す必要があります。Waitを早すぎるタイミングで呼び出すと、すべてのゴルーチンが終了する前にプログラムが終了してしまう可能性があります。

以上が、WaitGroupの一般的な落とし穴と注意点です。これらを理解し、適切に対処することで、WaitGroupを安全に使用することができます。次のセクションでは、WaitGroupとポインタを用いた並列処理の実践例について説明します。

実践:WaitGroupとポインタを用いた並列処理

ここでは、WaitGroupとポインタを用いて、複数のゴルーチンを効率的に管理する並列処理の実践例を見てみましょう。

以下に、5つのゴルーチンを起動し、それらがすべて終了するのを待つコードを示します。

package main

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

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()

    fmt.Printf("Worker %d starting\n", id)

    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    wg.Wait()
    fmt.Println("All workers done")
}

このコードでは、main関数内でWaitGroupを作成し、5つのゴルーチンを起動しています。各ゴルーチンは、一定時間待機した後に終了します。WaitGroupWaitメソッドは、すべてのゴルーチンが終了するのを待ち、その後で”すべてのワーカーが終了しました”と表示します。

このように、WaitGroupとポインタを用いることで、複数のゴルーチンを効率的に管理し、並列処理を行うことができます。これらの概念を理解し、適切に使用することで、Go言語の並列処理の力を最大限に引き出すことができます。

By quonta

Related Post

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です