LoginSignup
80
60

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-07-21

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

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

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

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

log.Println("the main goroutine")

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

  • channel の生成には組み込み関数の make を利用する
  • 値を送信したい箇所では channel <- (値) のように記述する
  • 値を受信したい箇所では <-channel のように記述することで値を参照できる
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 となるので, 受信側はそれを検知することもできる
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 を利用した場合, 送信側は受信側での受信を待たずに, 最大でバッファ長までブロックせずに値を送信することができる
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 では, 未受信の値でバッファが埋められている間はバッファに空きが生まれるまでブロックすることを利用して, 複数の並行処理の並行度を制限することができる
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")

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

  • forselect によるイベントループと, channel を渡すための channel (channel of channel) を利用することで, 複雑な排他制御を実装しなくても, 別の goroutine に対して同期的な通信が行える
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 などの仕組みを利用せずに済む

参考

80
60
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
80
60