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")
別の並行処理単位に対して同期的なリクエストを発行する
-
for
とselect
によるイベントループと, 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 などの仕組みを利用せずに済む