Go言語のチャネルのまとめ|ゴルーチン間でデータのやり取りをする方法
Go言語のチャネルについてまとめました。Goではゴルーチンとチャネルを利用することで簡単に並列実行させることが可能です。
Go言語のチャネルとは
Go言語のチャネル(channel)は、ゴルーチン(goroutine)間でデータを送受信するための機能です。
ゴルーチンは、Go言語の軽量スレッドで並列に動作します。チャネルは、スレッド間の安全な通信手段として使用します。
簡単に言えば、チャネルは、特定の型のデータを送受信するための通信パイプ(または、通信バッファ)のようなものになります。
ここでは、Goのチャネルの使い方について解説します。
channel(基本)
channelの生成
チャネルを生成するには、make
を使います。
チャネル名 := make(chan データ型)
または、
チャネル名 := make(chan データ型, バッファサイズ)
例えば、整数型のチャネルを作成する場合は以下のようになります。
ch := make(chan int)
また、バッファサイズを指定する場合は以下のようになります。
ch := make(chan int, 3)
バッファサイズに関しては、後で詳しく説明します。
channelを用いたデータ送受信
チャネルにデータを送る
チャネルにデータを送る場合は、チャネル名<-データ
を使います。
チャネル名 <- データ
チャネルからデータを受け取る
チャネルからデータを受け取る場合は、変数<-チャネル
になります。
変数 <- チャネル名
以下、goroutineとチャネルを利用してデータの送受信する例です。
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 100
}()
v := <-ch
fmt.Println(v) // 100
}
上記の例ではインラインで関数を定義し、go
でゴルーチンとして呼び出しています。
なお、チャネルからの受け取りでは2つの戻り値を受けることができます。2つ目の引数は、bool型で、受け取ったデータが送信されたデータかどうかをtrue/falseで返します。
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 100
}()
v, ok := <-ch
fmt.Println(v, ok) // 100 true
}
channelのクローズ
チャネルを明示的にクローズしたいん場合はclose()
を使います。
close(チャネル名)
先ほどの例をclose
を挿入して書き直すと以下のようになります。
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 100
}()
v := <-ch
fmt.Println(v) // 100
close(ch)
}
goroutineでのchannelの利用
channelのバッファについて
チャネルでは、バッファのサイズを指定しないと、受信側がデータを受け取るまで送信側はブロックされてしまいます。
連続で送信したい場合には、バッファサイズを指定する必要があります。例えばバッファサイズを1に指定すると、1つだけバッファされ、2つ目の送信時にブロックされるようになります。
これを試してみます。以下、実験コードです。
バッファを指定しない場合
以下のコードを実行すると、”send:
“は1度も表示されません。これは、その上のch <- i
で送信したデータが受信されるまでブロックしているからです。
package main
import (
"fmt"
"time"
)
func send(ch chan<- int) {
for i := 0; i < 10; i++ {
ch <- i
fmt.Println("send:", i)
}
}
func main() {
ch := make(chan int)
go send(ch)
time.Sleep(time.Second * 10)
}
バッファを1にした場合
バッファサイズを1にした場合は、send: 0
と表示されます。バッファが1つあるので2つ目の送信でブロックされました。
package main
import (
"fmt"
"time"
)
func send(ch chan<- int) {
for i := 0; i < 10; i++ {
ch <- i
fmt.Println("send:", i)
}
}
func main() {
ch := make(chan int, 1)
go send(ch)
time.Sleep(time.Second * 10)
}
// send: 0
バッファを5にした場合
バッファを5にすると、以下のようになります。このように、バッファサイズを指定することで、すぐにはブロックされないようにすることが可能です。
package main
import (
"fmt"
"time"
)
func send(ch chan<- int) {
for i := 0; i < 10; i++ {
ch <- i
fmt.Println("send:", i)
}
}
func main() {
ch := make(chan int, 5)
go send(ch)
time.Sleep(time.Second * 10)
}
// send: 0
// send: 1
// send: 2
// send: 3
// send: 4
送信側と受信側の処理速度に違いがある、処理の速度にばらつきがある場合などにバッファを用意することで、並列度を向上させることが可能です。
channelとselect文
select文を使うことで、複数のチャネルからのデータを待ち受けることが可能です。
以下は、send1
とはch1
で送受信、send2
とはch2
で送受信する例です。
受信側では、select
を使ってデータを受信しています。
このようにすることで、複数のチャネルのうち、データを準備できたチャネルからデータを受信して処理することが可能になります。
package main
import (
"fmt"
"sync"
)
func send1(ch chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 10; i++ {
ch <- i
}
}
func send2(ch chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 10; i++ {
ch <- 100 + i
}
}
func main() {
var wg sync.WaitGroup
ch1 := make(chan int)
ch2 := make(chan int)
wg.Add(2)
go send1(ch1, &wg)
go send2(ch2, &wg)
for i := 0; i < 20; i++ {
select {
case v1 := <-ch1:
fmt.Println("Received from ch1:", v1)
case v2 := <-ch2:
fmt.Println("Received from ch2:", v2)
}
}
wg.Wait()
}
実行結果は以下のようになります。
※なお、ch1とch2の受信順は実行のたびに変化します。
Received from ch2: 100
Received from ch2: 101
Received from ch1: 0
Received from ch1: 1
Received from ch1: 2
Received from ch1: 3
Received from ch1: 4
Received from ch1: 5
Received from ch1: 6
Received from ch1: 7
Received from ch1: 8
Received from ch1: 9
Received from ch2: 102
Received from ch2: 103
Received from ch2: 104
Received from ch2: 105
Received from ch2: 106
Received from ch2: 107
Received from ch2: 108
Received from ch2: 109
プログラム中のsync.WaitGroupは、同期のための機構です。例ではAdd(2)
とし、send0, send1
でそれぞれ、wg.Done()
を実行して完了を通知しています。wg.Wait()
は、Add
で指定された数のDone
が実行されるまで待ちます。
これにより、snnd0/1
が実行完了するまで待つ形になります
上のプログラムでは、手前でチャネルの出力を全て受け取ってから終了しているので必要ないですが、とりあえず。
その他
for~rangeによる繰り返しにもチャネルを利用することが可能です。こちらについては以下を参考にしてください。
まとめ
以上、チャネルについて説明してきました。ゴルーチンとチャネルのおかげでGo言語では比較的手軽に並列実行を記述することが可能です。このあたりはよくできている言語だなと感じています。