Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

前編の続きです。

前回のデザパタを、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のタイムアウトとの合わせ技をして、時間のかかるレプリカは切り捨てることで、より効率的に検索できるわけです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away