goroutineあれこれ2

# お題
[前回](https://qiita.com/sky0621/items/601deaa2007c28ed16d6)の続き。
今回は、チャネル使ってあれこれ。
ただ、今回も浅いです。

# 関連記事Index
- [goroutineあれこれ3](https://qiita.com/sky0621/items/5089d3c265b02763e600)
- [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
```

# 実践
## ■２つのチャネル間でやりとり

### $共通に使う関数$

ループ内でチャネル「ch」からの入力を待つ。
入力があったら指定のワード「word」を出力して100ミリ秒スリープ。
スリープ後にチャネル「ch」に（他の`goroutine`(自身かもしれないけど)の処理を発火させるために）空の構造体を入力。

```go
func sub(word string, ch chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()

	for i := 0; i < 5; i++ {
		select {
		case <-ch:
			fmt.Println(word)
			time.Sleep(100 * time.Millisecond)
			ch <- struct{}{}
		}
	}
}
```

### No.01: 待ち合わせピンポン（失敗版）

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

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

	wg.Add(1)
	go sub("ping", ch, wg)

	wg.Add(1)
	go sub("     pong", ch, wg)

	wg.Wait()
}
```
https://play.golang.org/p/90EE5J9ZTUr

これ、失敗します。

```
$ go run main.go 
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc42008601c)
	/usr/lib/go-1.10/src/runtime/sema.go:56 +0x39
sync.(*WaitGroup).Wait(0xc420086010)
	/usr/lib/go-1.10/src/sync/waitgroup.go:129 +0x72
main.main()
	/work/src/golang/src/github.com/sky0621/tips-go/goroutine/no03/no0301/main.go:20 +0x11c

goroutine 18 [chan receive]:
main.sub(0x4b372b, 0x4, 0xc42007a060, 0xc420086010)
	/work/src/golang/src/github.com/sky0621/tips-go/goroutine/no03/no0301/main.go:28 +0x6c
created by main.main
	/work/src/golang/src/github.com/sky0621/tips-go/goroutine/no03/no0301/main.go:15 +0xb6

goroutine 19 [chan receive]:
main.sub(0x4b3d63, 0x9, 0xc42007a060, 0xc420086010)
	/work/src/golang/src/github.com/sky0621/tips-go/goroutine/no03/no0301/main.go:28 +0x6c
created by main.main
	/work/src/golang/src/github.com/sky0621/tips-go/goroutine/no03/no0301/main.go:18 +0x10e
exit status 2
```

なぜか？
メインから（チャネルへの入力待ちを行う）２つの`goroutine`を起動したものの、肝心のチャネルへの入力が一切ないから（だと思う）。

ちなみに、`sub`関数の`select`に`default`を書いてやれば↑のエラーは起きなくなる。

```go
func sub(word string, ch chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()

	for i := 0; i < 5; i++ {
		select {
		case <-ch:
			fmt.Println(word)
			time.Sleep(100 * time.Millisecond)
			ch <- struct{}{}

ここ->		default:
ここ->			fmt.Println("skip")

		}
	}
}
```
https://play.golang.org/p/Kd-Qt1nGOqG

すると、こうなる。

```
$ go run main.go 
skip
skip
skip
skip
skip
skip
skip
skip
skip
skip
```

この手法は、チャネルへの入力があるたびに何度でも発動する処理にしたい時に使える。
（`for`を無限ループにしておくこととセット）

### No.02: 待ち合わせピンポン（成功版）

目的は２つの`goroutine`間でピンポン打ち合うことなので、No.01での失敗を修正する。
と言っても、No.01で書いた通り、２つの`goroutine`が機能しだすためのきっかけをメイン関数で与えるだけ。

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

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

	wg.Add(1)
	go sub("ping", ch, wg)

	wg.Add(1)
	go sub("     pong", ch, wg)

ここ->	// 最初のきっかけを与えてやる
ここ->	ch <- struct{}{}

	wg.Wait()
}
```

すると、２つの`goroutine`が動き出す。

```
$ go run main.go 
     pong
ping
     pong
ping
     pong
ping
     pong
ping
     pong
ping
```

### No.03: 待ち合わせピンポンパン

念の為、起動する`goroutine`を５つにしても機能することを確認する。

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

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

	words := []string{
		"ping",
		"     pong",
		"          pang",
		"               peng",
		"                    pung",
	}

	for _, word := range words {
		wg.Add(1)
		go sub(word, ch, wg)
	}

	// 最初のきっかけを与えてやる
	ch <- struct{}{}

	wg.Wait()
}
```
https://play.golang.org/p/mhWeBanm2v3

```
$ go run main.go 
                    pung
     pong
ping
               peng
          pang
                    pung
     pong
ping
               peng
          pang
                    pung
     pong
ping
               peng
          pang
                    pung
     pong
ping
               peng
          pang
                    pung
     pong
ping
               peng
          pang
```

### No.04: 待ち合わせないピンポンパン

「待ち合わせ」る実装の例としてピンポンしていたけど、この事例は本来であれば互いの処理を待ち合わせる必要はまったくない。
というわけで、互いを待たずして単純に平行に走らせるとこうなる。

`sub`関数から`select`を除去。

```go
func sub(word string, ch chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()

	for i := 0; i < 5; i++ {
		fmt.Println(word)
		time.Sleep(100 * time.Millisecond)
	}
}
```
https://play.golang.org/p/wAmRPjFulNk

結果はNo.03と同じく５種類のワードを５回ずつ出力するけど、当然、No.03より処理時間が短い。（互いを待たないから）

```
$ go run main.go 
                    pung
     pong
          pang
ping
               peng
     pong
                    pung
ping
          pang
               peng
     pong
               peng
                    pung
ping
          pang
                    pung
          pang
     pong
ping
               peng
                    pung
          pang
     pong
               peng
ping
```

# まとめ
浅いレベルの話を続けているけど、少しずつ応用編を書いていければと思う。
もっといろんなパターンをいち早く知りたい人は↓を見るといいと思う。
https://mattn.kaoriya.net/software/lang/go/20180531104907.htm
https://mattn.kaoriya.net/software/lang/go/20160706165757.htm
https://mattn.kaoriya.net/software/lang/go/20171221111857.htm