2018年12月25日火曜日

goroutine の並列処理の終了を待機する方法

概要

例えば並列で 5 つの goroutine を回す場合、すべての goroutine の終了を待ってからメインの処理に戻りたい場合があると思います
今回は sync パッケージと errgroup パッケージを使った 2 つのサンプルコードを紹介します

環境

  • macOS 10.14.1
  • golang 1.11.2
  • errgroup 42b3178

sync.waitGroup

sync パッケージはデフォルトで使えるライブラリです
これでも goroutine の並列処理の完了を待つことができます

  • mkdir -p go/src/github.com/hawksnowlog/eg_test/
  • touch go/src/github.com/hawksnowlog/eg_test/main.go
  • vim go/src/github.com/hawksnowlog/eg_test/main.go
package main

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

func waiter(i int) {
    t := time.Duration(i)
    time.Sleep(t * time.Second)
    fmt.Println(i)
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            waiter(i)
        }(i)
    }
    wg.Wait()
    fmt.Println("end")
}
  • go build github.com/hawksnowlog/eg_test
  • go install github.com/hawksnowlog/eg_test
  • $GOPATH/bin/eg_test

これで 0 から 4 までのカウントが終了してから end が表示されると思います

まず sync.WaitGroup の変数を作成します
作成した waitGroup の変数に対して goroutine を開始する前に wg.Add(1) でインクリメントします
そして goroutine 内で defer を使って関数が終了した際に必ず wg.Done() をコールします
あとは同期したい部分で wg.Wait() することですべての goroutine が終了するまで待機します

errgroup インストール

  • go get -u golang.org/x/sync/errgroup

errgroup.Group

同じようなことを errgroup.Group を使ってもできます
こちらを使ったほうが簡潔に書けます

package main

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

func waiter(i int) error {
    t := time.Duration(i)
    time.Sleep(t * time.Second)
    fmt.Println(i)
    return nil
}

func main() {
    eg := errgroup.Group{}
    for i := 0; i < 5; i++ {
        val := i
        eg.Go(func() error {
            return waiter(val)
        })
    }
    err := eg.Wait()
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println("end")
}

goroutine でコール関数が error を返却する必要があります
メイン処理では各 goroutine が error を返却しているかどうか判定することができるようになっています
また Add や Done の記述も不要になっています
ちなみに val := i で別の変数に格納してから goroutine に値を渡さないとすべて 5 で値が渡ってしまうので注意が必要です

最後に

goroutine とメイン側の処理を同期する方法を紹介しました
機能的には errgroup のほうがいろいろできそうです
単純な同期であれば sync で十分かもしれません

今回のサンプルは goroutine 内のタイムアウトを考慮できていません
チャネルなどと組み合わせるか goroutine で呼び出している関数側でタイムアウト時の処理を考慮するようにしましょう

参考サイト

0 件のコメント:

コメントを投稿