LoginSignup
43
41

More than 5 years have passed since last update.

Go言語の並行処理デザインパターン by Rob Pike 後編

Posted at

前編の続きです。

前回のデザパタを、Google検索を例に適用しましょうという話。

Google 1.0

まずは並行処理が入っていない、そのまま逐次処理の例です。
ダミーの検索関数type Search func(query string) Resultを返す、fakeSearch関数を作っておきます。
ダミーの検索関数Searchは、time.Sleepで一定時間スリープして、検索しているフリをします。

fake.go
package fake

import (
    "fmt"
    "math/rand"
    "time"
)

var (
    Web   = fakeSearch("web")
    Image = fakeSearch("image")
    Video = fakeSearch("video")
)

type Search func(query string) Result

func fakeSearch(kind string) Search {
    return func(query string) Result {
        time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
        return Result(fmt.Sprintf("%s result for %q\n", kind, query))
    }
}

type Result string

メイン関数です。Google関数にて、先ほどのダミー検索関数を逐次処理で3回呼び出しています。

google1/main.go
package main

import (
    "runtime"
    "time"
    "math/rand"
    "fmt"
    "github.com/tfutada/robpike/google/fake"
)

func main() {

    runtime.GOMAXPROCS(runtime.NumCPU())
    rand.Seed(time.Now().UnixNano())

    start := time.Now()
    results := Google("golang")
    elapsed := time.Since(start)
    fmt.Println(elapsed)
    fmt.Println(results)
}

func Google(query string) (results []fake.Result) {
    results = append(results, fake.Web(query))
    results = append(results, fake.Image(query))
    results = append(results, fake.Video(query))
    return
}

実行結果

/usr/local/go/bin/go run /Users/taka/go/src/github.com/tfutada/robpike/google/google1/main.go
160.479531ms
[web result for "golang"
 image result for "golang"
 video result for "golang"
]

Process finished with exit code 0

Google 2.0

Goルーチンを使用して並行処理します。前編のFanInパターンを使用します。
ダミーの検索関数をそれぞれ無名関数でラップし、チャネル経由で結果を返します。

google2/main.go
func Google(query string) (results []fake.Result) {

    c := make(chan fake.Result)
    go func() { c <- fake.Web(query) }()
    go func() { c <- fake.Image(query) }()
    go func() { c <- fake.Video(query) }()
    for i := 0; i < 3; i++ {
        results = append(results, <-c)
    }

    return
}

実行結果

前回より早くなりました。一番検索結果が遅いものが、全体の処理時間になります。

/usr/local/go/bin/go run /Users/taka/go/src/github.com/tfutada/robpike/google/google2/main.go
82.139627ms
[web result for "golang"
 image result for "golang"
 video result for "golang"
]

Process finished with exit code 0

Google 2.1

前回のtimeoutパターンで、ある一定時間経過したら、検索を打ち切る処理を入れます。

timeout/main.go
func Google(query string) (results []fake.Result) {

    c := make(chan fake.Result)
    go func() { c <- fake.Web(query) }()
    go func() { c <- fake.Image(query) }()
    go func() { c <- fake.Video(query) }()

    timeout := time.After(80 * time.Millisecond)

    for i := 0; i < 3; i++ {
        select {
        case s := <- c:
            results = append(results, s)
        case <- timeout:
            fmt.Println("Timed out!")
            return
        }
    }

    return
}

実行結果

処理が80msで打ち切られ、image検索が実行されてないのがわかります。

/usr/local/go/bin/go run /Users/taka/go/src/github.com/tfutada/robpike/google/google3/main.go
Timed out!
80.690454ms
[web result for "golang"
 video result for "golang"
]

Process finished with exit code 0

Google 3.0

次のチューニングは、検索処理の冗長化を行うものです。
同じ検索処理を並行して複数実行し、一番早く帰ってきたものを正として使用します。

first/main.go
func main() {

    runtime.GOMAXPROCS(runtime.NumCPU())
    rand.Seed(time.Now().UnixNano())

    start := time.Now()

    results := First("golang",
        fake.FakeSearch("replica 1"),
        fake.FakeSearch("replica 2"),
        fake.FakeSearch("replica 3"),
        fake.FakeSearch("replica 4"),
        fake.FakeSearch("replica 5")) // 注) fakeSearch -> FakeSearch

    elapsed := time.Since(start)
    fmt.Println(elapsed)
    fmt.Println(results)
}

func First(query string, replicas ...fake.Search) fake.Result {

    c := make(chan fake.Result)
    searchReplica := func(i int) {c <- replicas[i](query)}
    for i := range replicas {
        go searchReplica(i) // クロージャだとうまくいかない。。。
    }
    return <-c
}

// 最初のfakeSearchを大文字に変えて、メイン関数から見れるようにしました。
func FakeSearch(kind string) Search {
    // 同じ

実行結果

/usr/local/go/bin/go run /Users/taka/go/src/github.com/tfutada/robpike/google/google3/main.go
2.422057ms
replica 2 result for "golang"


Process finished with exit code 0

これを踏まえて、先ほどのGoogle 2.1のタイムアウトとの合わせ技をして、時間のかかるレプリカは切り捨てることで、より効率的に検索できるわけです。

43
41
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
43
41