goroutine と channel を使った並行処理のパターン

Go 言語では goroutine と channel を利用して, 並行処理を記述することができる. ここではいくつかの並行処理のパターンにおける Go 言語による実装をまとめてみる.

## 独立した処理を並行に進める

- `go` キーワードの直後に実行したい関数の呼び出し式を記述すると, 新しく生成された goroutine 上で実行される

```go:independent_goroutines.go
// Run another goroutine independently

go func() { 
  log.Println("another goroutine")
}()

log.Println("the main goroutine")
```

## 別の並行処理単位から値を受け取る

- channel の生成には組み込み関数の `make` を利用する
- 値を送信したい箇所では `channel <- (値)` のように記述する
- 値を受信したい箇所では `<-channel` のように記述することで値を参照できる

```go:receive_a_result.go
// Run another goroutine and receive a value

resc := make(chan int)

go func(resc chan int) {
  log.Println("another goroutine")
  resc <- 42
}(resc)

log.Println("the main goroutine, received value:", <-resc)
```

## 別の並行処理単位から逐次的に値を受け取る

- goroutine 間では, channel を通じて複数回に渡り値を送受信することができる
- それ以上送信が必要ない段階まで進んだら, 組み込み関数 `close` によりその旨を伝えることができる
- 受信側では `range` ループにより受信した値を読み出すことができる
- `close` によりそれ以上使われなくなった channel は `<-channel` の第二戻り値が `false` となるので, 受信側はそれを検知することもできる

```go:receive_sequential_results.go
// Run another goroutine and receive sequential values

resc := make(chan int)

go func(resc chan int) {
  for i := 0; i < 42; i++ {
    log.Println("another goroutine is sending:", i)
    resc <- i
  }
  close(resc)
}(resc)

for i := range resc {
  log.Println("the main goroutine receives:", i)
}

//for {
//  i, ok := <-resc
//  if !ok {
//    break
//  }
//  log.Println("the main goroutine receives:", i)
//}

```

- 複数回に渡って値を渡す場合には buffered channel と呼ばれる channel を利用することもできる
- buffered channel を作成するには `make` の第二引数にバッファ長を渡す
- buffered channel を利用した場合, 送信側は受信側での受信を待たずに, 最大でバッファ長までブロックせずに値を送信することができる

```go:receive_results_with_buffered_channel.go
// Run another goroutine and receive sequential values with buffered channel

resc := make(chan int, 8)

go func(resc chan int) {
  for i := 0; i < 42; i++ {
    log.Println("another goroutine is sending:", i)
    resc <- i
  }
  close(resc)
}(resc)

for i := range resc {
  log.Println("the main goroutine receives:", i)
}
```

## 並行度を制限して複数の並行処理を行う

- buffered channel では, 未受信の値でバッファが埋められている間はバッファに空きが生まれるまでブロックすることを利用して, 複数の並行処理の並行度を制限することができる

```go:semaphore.go
// Run limited number of goroutines simultaneously

sem := make(chan struct{}, 4) // concurrency: 4

for i := 0; i < 42; i++ {
  // Acquire a semaphore
  sem <- struct{}{}

  go func(i int, sem chan struct{}) {
    log.Println("goroutine: ", i)
    // Release a semaphore
    <-sem
  }(i, sem)
}

log.Println("the main goroutine")
```

## 別の並行処理単位に対して同期的なリクエストを発行する

- `for` と `select` によるイベントループと, channel を渡すための channel (channel of channel) を利用することで, 複雑な排他制御を実装しなくても, 別の goroutine に対して同期的な通信が行える

```go:synchronous_request.go
// Issue a synchronous request to another goroutine

reqc := make(chan chan int)

go func(reqc chan chan int) {
  for {
    select {
    case resc := <-reqc:
      log.Println("another goroutine receives a request")
      resc <- 42
    }
  }
}(reqc)

request := func() int {
  req := make(chan int)
  reqc <- req
  return <-req
}

log.Println("the main goroutine receives a response:", request())
```

# まとめ

- Go では言語レベルで並行処理を記述するための仕組みが備わっている
- Go での並行処理では, 適切に channel を利用することで, Lock や Callback などの仕組みを利用せずに済む

# 参考

- [Go Concurrency Patterns](https://talks.golang.org/2012/concurrency.slide)
- [Advanced Go Concurrency Patterns](https://talks.golang.org/2013/advconc.slide)
- [Successful Go Program Design, 6 Years On](https://www.infoq.com/presentations/go-patterns)
- [Effective Go](https://golang.org/doc/effective_go.html)