17
7

Go言語で並行処理を実装する場合は、これまでsync.WaitGroupと同時実行数制御のsemaphoreを組み合わせて実装していました。しかし、errgroupを使うことで、より簡潔に実装できることを最近知ったので、実装方法を簡単にまとめます。

本記事でためした環境のGo version: 1.22.0

errgroupの基本的な使い方

  • Go()関数で並行処理を実行する
  • Wait()関数で全ての並行処理が終了するのを待つ
  • SetLimit()関数で同時実行数を制限

以下に例を記載します。

以下に基本的な例を示します。

package main

import (
    "fmt"
    "sync"
    "golang.org/x/sync/errgroup"
)

func main() {
    var g errgroup.Group

    tasks := []func() error{
        func() error {
            // タスク1
            fmt.Println("Task 1")
            return nil
        },
        func() error {
            // タスク2
            fmt.Println("Task 2")
            return nil
        },
        func() error {
            // タスク3
            fmt.Println("Task 3")
            return nil
        },
    }

    for _, task := range tasks {
        g.Go(func() error {
            return task()
        })
    }

    if err := g.Wait(); err != nil {
        fmt.Printf("error: %v\n", err)
    }
    fmt.Println("success!!!")
}
Output
Task 3
Task 1
Task 2
success!!!

上記のコードでは、3つのタスクを並行して実行し、全てのタスクが完了するまで待ちつづけます。

SetLimit()による同時実行の制御

CPUなどリソースへの負担を避けたいときは、SetLimit()を使用して同時実行数を制御することができます。

package main

import (
    "fmt"
    "golang.org/x/sync/errgroup"
    "time"
)

func main() {
    var g errgroup.Group

    // 同時実行数を2に制限
    g.SetLimit(2)

    for i := range 5 {
        g.Go(func() error {
            fmt.Printf("Task %d started\n", i)
            time.Sleep(2 * time.Second)
            fmt.Printf("Task %d finished\n", i)
            return nil
        })
    }

    if err := g.Wait(); err != nil {
        fmt.Printf("error: %v\n", err)
    } 
    fmt.Println("success!!!")
}
Output
Task 1 started
Task 0 started
Task 1 finished
Task 2 started
Task 0 finished
Task 3 started
Task 2 finished
Task 4 started
Task 3 finished
Task 4 finished
success!!!

この例では、5つのタスクがある中で同時に実行されるのは最大2つまでとなります。
分かりにくいですがOutputをみてみると、最初にTask1,0がスタートして、Task1が終了時にTask2がスタートしています。
ちゃんと同時実行数が2に制御されていますね。

for文で並列処理を行う際の注意点

go1.21以前では、forの各反復ではループ変数(iやv)が同じインスタンスを共有するため、for内のgoroutineに正しく変数を渡すことができません。
この問題についてはググるとたくさん出てくるので割愛します。

go1.22ではこの問題が解消されています。
内容は、以下リリースノートに詳しく記載されています。
https://tip.golang.org/doc/go1.22

もしgo1.21以前の場合は以下のように、loop変数を束縛する必要があります。

for i := range 5 {
    i := i  // 新しい変数'i'でloop変数'i'を束縛
    g.Go(func() error {
        fmt.Printf("Task %d\n", i)
        return nil
    })
}

最後に

今回は、Goのerrgroupパッケージでの並行処理の実装について紹介しました。

弊社HRBrainでは、Goでのバックエンド開発が行われており、エンジニアの仲間を募集しています。

弊社に興味がある方は、ぜひHPだけでも見に来てください。

17
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
7