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

[前編](http://qiita.com/tfutada/items/a289628d8b2d0af6152d)の続きです。

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

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

```go: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回呼び出しています。

```go: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
}
```

### 実行結果

```bash
/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パターンを使用します。
ダミーの検索関数をそれぞれ無名関数でラップし、チャネル経由で結果を返します。

```go: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
}
```

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

```bash
/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パターンで、ある一定時間経過したら、検索を打ち切る処理を入れます。

```go: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
}
```

### [実行結果](https://youtu.be/f6kdp27TYZs?t=1954)

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

```bash
/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

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

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