goroutineあれこれ3

# お題
[前回](https://qiita.com/sky0621/items/7a5fe811bd807647353c)の続き。

# 関連記事Index
- [goroutineあれこれ2](https://qiita.com/sky0621/items/7a5fe811bd807647353c)
- [goroutineあれこれ](https://qiita.com/sky0621/items/601deaa2007c28ed16d6)

# 開発環境
### # OS
```
$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="17.10 (Artful Aardvark)"
```

### # 言語
```
$ go version
go version go1.10 linux/amd64
```

# 実践
## ■一度に起動する`goroutine`数を制御しながら大量のワークロードを捌く

大量といいつつコード例では25ループ回すだけだけど。
実際には、例えばメッセージキューをポーリングしつつ、複数メッセージを受信するたびに捌いていくのとかに使える。
（大抵はフレームワークでやってしまうだろうけど）HTTPリクエストを捌くのにも使えると思う。

```go
func main() {
	wg := &sync.WaitGroup{}

	ch := make(chan struct{}, 5)

	for i := 0; i < 25; i++ {
		wg.Add(1)

		ch <- struct{}{} // 5個までは詰められる（でも、それ以上はチャネルが空くまで詰められずにここで待機状態となる）
		go sub(i, ch, wg)
	}

	wg.Wait()
}

func sub(i int, ch chan struct{}, wg *sync.WaitGroup) {
	defer func() {
		<-ch // 処理後にチャネルから値を抜き出さないと、次の goroutine が起動できない

		wg.Done()
	}()

	fmt.Println(i)
	time.Sleep(2 * time.Second)
}
```
https://play.golang.org/p/Qwz_grTWFYh

```
$ go run main.go 
4
1
2
3
0   <- この後、２秒スリープ
6
8
7
5
9   <- この後、２秒スリープ
10
11
12
14
13  <- この後、２秒スリープ
15
16
17
18
19  <- この後、２秒スリープ
20
21
23
22
24  <- この後、２秒スリープ
```

## ■時間が来るまで作業を続ける系

実際の業務では、こんな使い方はしないかな。
go1.7から標準パッケージになった`context`を使ってタイムアウトを設定。
メインで起動し、同じくメインの終了を`context`のタイムアウト発動待ちとする。
サブの`goroutine`では時間が来るまで「a」を吐き続ける。

```go
func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	go sub(ctx)

	select {
	case <-ctx.Done():
		fmt.Println("END")
	}
}

func sub(ctx context.Context) {
	for {
		fmt.Printf("a ")
		time.Sleep(1 * time.Second)
	}
}
```

```
$ go run main.go 
a a a a a a a a a a END
```

## ■３重くらいの階層構造を辿って`goroutine`起動

例えば指定のディレクトリ階層配下のファイルそれぞれについて何かしらの処理を行いたい時、
その１つ１つの処理が他の処理と関連なく、平行に行っていい場合には階層をたどりながら全ての処理で`goroutine`実行させれば、処理時間の短縮につながる。
ただし、今回の例では含めていないけど、実際には無制限に`goroutine`起動させると、処理するファイルが１００個あったら１００`goroutine`、１０００個あったら１０００`goroutine`を同時に起動させることになるので、
チャネルを使った`goroutine`の実行数制御とも絡めて使うべき。

https://play.golang.org/p/9uM_bNtD94S

```go
func main() {
	wg := &sync.WaitGroup{}

	components := createComponents()

	for _, cmp := range components {
		wg.Add(1)
		go cmp.exec(wg)
	}

	wg.Wait()
}
```

↑メイン関数での`goroutine`起動部分は何度も書いた通り。
今回の肝は、↓の「`createComponents()`」の内容と、[コンポジットパターン](http://www.techscore.com/tech/DesignPattern/Composite.html/)の構造の中で `composite` が実装する `exec()` の中身。

```go
func createComponents() []component {
	// けっこうエグい構造を作っておく
	return []component{
		newComposite("A", []component{
			newComposite("A-a", []component{
				newLeaf("A-a-1"),
			}),
			newComposite("A-b", []component{
				newLeaf("A-b-1"),
				newLeaf("A-b-2"),
			}),
		}),
		newComposite("B", []component{
			newLeaf("B-a"),
			newComposite("B-b", []component{
				newComposite("B-b-1", []component{
					newLeaf("B-b-1-1"),
					newLeaf("B-b-1-2"),
					newComposite("B-b-1-3", []component{
						newLeaf("B-b-1-3-1"),
					}),
				}),
			}),
			newLeaf("B-c"),
		}),
		newLeaf("C"),
		newComposite("D", []component{
			newComposite("D-d", []component{
				newLeaf("D-d-1"),
				newLeaf("D-d-2"),
				newLeaf("D-d-3"),
				newLeaf("D-d-4"),
			}),
		}),
	}
}
```

コンポジット構造を定義。

```go
type component interface {
	exec(wg *sync.WaitGroup)
}

type composite struct {
	name   string
	childs []component
}

func (c *composite) exec(wg *sync.WaitGroup) {
	defer wg.Done()
	time.Sleep(1 * time.Second)
	fmt.Printf("[COMPOSITE]%s\n", c.name)

	wg2 := &sync.WaitGroup{}
	for _, child := range c.childs {
		wg2.Add(1)
		go child.exec(wg2)
	}
	wg2.Wait()
}

type leaf struct {
	name string
}

func (l *leaf) exec(wg *sync.WaitGroup) {
	defer wg.Done()
	time.Sleep(1 * time.Second)
	fmt.Printf("[LEAF]%s\n", l.name)
}
```

実行すると、こうなる。

```
$ go run main.go 
[COMPOSITE]D
[LEAF]C
[COMPOSITE]A
[COMPOSITE]B
[LEAF]B-a
[COMPOSITE]B-b
[COMPOSITE]A-b
[LEAF]B-c
[COMPOSITE]A-a
[COMPOSITE]D-d
[LEAF]D-d-2
[LEAF]D-d-3
[LEAF]A-b-2
[LEAF]D-d-1
[COMPOSITE]B-b-1
[LEAF]D-d-4
[LEAF]A-b-1
[LEAF]A-a-1
[LEAF]B-b-1-2
[LEAF]B-b-1-1
[COMPOSITE]B-b-1-3
[LEAF]B-b-1-3-1
```

# まとめ
あまりまとまりなく、思いつくままに`goroutine`使う例を書いていったけど、ひとまずこのへんにしておこう。