Go で errgroup を使って直列 vs 並列

直列 vs 並列シリーズ第 3 弾。 あんまり errgroup を使ったサンプルがなかったのでやってみた。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "sync"
    "time"

    "golang.org/x/sync/errgroup"
)

func createUrl(sec int) string {
    return fmt.Sprintf("http://httpbin.org/delay/%d", sec)
}

func requestBody(url string) (string, error) {
    response, err := http.Get(url)
    if (err != nil) {
        return "", err
    }
    defer response.Body.Close()

    bytes, err := ioutil.ReadAll(response.Body)
    if (err != nil) {
        return "", err
    }

    return string(bytes), nil
}

// errgroup を使って非同期に関数を実行し、結果を待つ
func awaitAll(funcs []func() error) error {
    eg := errgroup.Group{}
    for _, fn := range funcs {
        eg.Go(fn)
    }

    if err := eg.Wait(); err != nil {
        return err
    }
    return nil
}
func serial(urls []string) ([]string, error) {
    bodies := make([]string, len(urls))

    for _, url := range urls {
        body, err := requestBody(url);
        if (err != nil) {
            return nil, err
        }
        // fmt.Println(body)
        bodies = append(bodies, body)
    }

    return bodies, nil
}

func parallel(urls []string) ([]string, error) {
    mutex := new(sync.Mutex)
    bodies := make([]string, 0, len(urls))
    funcs := make([]func() error, 0, len(urls))

    for _, url := range urls {
        fn := func() error {
            body, err := requestBody(url);
            if (err != nil) {
                return err
            }

            // fmt.Println(body)
            mutex.Lock()
            bodies = append(bodies, body)
            mutex.UnLock()
            return nil
        }
        funcs = append(funcs, fn)
    }
    if err := awaitAll(funcs); err != nil {
        return nil, err
    }

    return bodies, nil
}

func main() {
    max := 4
    urls := make([]string, 0, max)
    for i := 1; i <= max; i++ {
        url := createUrl(i)
        urls = append(urls, url)
    }

    fmt.Println("serial")
    startTime := time.Now()
    serial(urls)
    fmt.Println(time.Now().Sub(startTime))

    fmt.Println("parallel")
    startTime = time.Now()
    parallel(urls)
    fmt.Println(time.Now().Sub(startTime))
}

動かしてみる

$ go version
go version go1.9.2 linux/amd64
$ go get golang.org/x/sync/errgroup
$ go run concurrent.go
serial
11.157001925s
parallel
4.398581223s

ちゃんと並列で実行されてる。