goroutineあれこれ

# お題
表題の通り。
goroutineを使った実装パターンを知りたければ下記を読むといい。
https://mattn.kaoriya.net/software/lang/go/20180531104907.htm
が、パターンはパターンとして、`goroutine`をそもそも使わないケースから始めて、使うとどうなるか、うまく動かないケースも含めて試してみようと思う。

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

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

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

# 実践
## $共通に使う関数$
100ミリ秒のスリープを入れつつ５回「ping」を出力する`ping()`関数と同様に「pong」を出力する`pong()`関数を用意

```go
func ping() {
	for i := 0; i < 5; i++ {
		fmt.Println("ping")
		time.Sleep(100 * time.Millisecond)
	}
}

func pong() {
	for i := 0; i < 5; i++ {
		fmt.Println("     pong")
		time.Sleep(100 * time.Millisecond)
	}
}
```

## ■関数の前に「go」と付けるだけでメイン関数と別の `goroutine` が生成される

### No.01: goroutine使わない版
```go
func main() {
	ping()
	pong()
}
```
https://play.golang.org/p/gJLWwh8Z9Ak

当然のように５回「ping」の後、５回「pong」を出力（何回やっても結果は同じ）

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

### No.02: 先行して実行する関数をgoroutine化
```go
func main() {
	go ping()
	pong()
}
```
https://play.golang.org/p/0PeT8eIzKQx

`ping()`関数はメイン処理をブロックせずに実行されるようになったため、即座に`pong()`関数の実行も始まる。

＜１回目＞

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

＜２回目＞

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

＜３回目＞

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

### No.03: 後攻の関数をgoroutine化
```go
func main() {
	ping()
	go pong()
}
```
https://play.golang.org/p/Jpkn-BZLK3X

後攻の関数がメイン関数をブロックしなくなると、それ以降にメイン関数の処理がないため、メイン関数が終了する。
メイン関数が終了すると、goroutineも終了するため、５回の「ping」を出力したら（「pong」の出力は一切なしで）終了。

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

### No.04: 先行・後攻の関数をともにgoroutine化
```go
func main() {
	go ping()
	go pong()
}
```
https://play.golang.org/p/Ievs4LOjqpl

メイン関数をブロックする処理がなくなるので`ping()`関数ないし`pong()`関数が出力する前にメイン関数が終了する。

```
$ go run main.go 

```

## ■生成した `goroutine` の終了を待ってメイン関数を終了させる
### No.05: 2つのgoroutineの終了をメイン関数で待機（Sleep編）
```go
func main() {
	go ping()
	go pong()
	time.Sleep(3 * time.Second)
}
```
https://play.golang.org/p/CwNYzpoUdfg

`goroutine` 生成から３秒経過したら（`goroutine`ごと）メイン関数が終了
ただし、このやり方は悪手。

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

### No.06: 2つのgoroutineの終了をメイン関数で待機（WaitGroup編）
```go
func main() {
	wg := &sync.WaitGroup{}
	wg.Add(1)
	go ping(wg)
	wg.Add(1)
	go pong(wg)
	wg.Wait()
}

func ping(wg *sync.WaitGroup) {
	defer wg.Done()

	for i := 0; i < 5; i++ {
		fmt.Println("ping")
		time.Sleep(100 * time.Millisecond)
	}
}

func pong(wg *sync.WaitGroup) {
	defer wg.Done()

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

ちゃんとピンポン出る。

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

### No.07: 2つのgoroutineの終了をメイン関数で待機（Channel編）
```go
func main() {
	ch := make(chan struct{}, 2)
	go ping(ch)
	go pong(ch)
	<-ch
}

func ping(ch chan struct{}) {
	for i := 0; i < 5; i++ {
		fmt.Println("ping")
		time.Sleep(100 * time.Millisecond)
	}
	ch <- struct{}{}
}

func pong(ch chan struct{}) {
	for i := 0; i < 5; i++ {
		fmt.Println("     pong")
		time.Sleep(100 * time.Millisecond)
	}
	ch <- struct{}{}
}
```
https://play.golang.org/p/1GRUcD2_y54

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

# まとめ
goroutineやChannelが真価を発揮する大分手前の時点だけど、今回はさわりだけ。