Go

Goのchannelの送受信用の型について

More than 3 years have passed since last update.


初めに

Goにはchannelと呼ばれる、並行処理用のキューがあります。

基本的なことは他の記事に譲るとして、

これを使うと簡単に同期的にキューが扱え、ブロッキングもしてくれるので並行同期処理をする場合に(Javaなどに比べ)かなり楽に書けます。


channelの例


標準のchannel

// 容量10のchannel作成

c := make(chan int, 10)

// キューに送信
c <- 10

// キューから受信し、値を出力
// キューに値がなければデッドロッグ
fmt.Println(<-c) //10


実は、channelには送信専用・受信専用の型(?)があります。

Effective Go

https://golang.org/doc/effective_go.html#channels

や記事などにもあまり書かれてないので

記載したいと思います。


送信専用のchannel

(コンパイルエラーになります。)


送信専用のchannel

// 送信専用の容量10のchannel作成

c := make(chan<- int, 10)

// キューに送信
c <- 10

// 送信専用なのでコンパイルエラーになる。
fmt.Println(<-c)



受信専用のchannel

(コンパイルエラーになります。)


受信専用のchannel

// 受信専用の容量10のchannel作成

c := make(<-chan int, 10)

// 受信専用なのでコンパイルエラー
c <- 10

fmt.Println(<-c)



使いどころ

上だけ見ると、コンパイルエラーになるので使いどころあるの?という感じですが。

実は、

「標準のchannel型」 → 「入力 or 出力専用のchannel型」

への代入ができます。

(それ以外はできないようです。)

うまく使うと型レベルで宣言し、コンパイル時にチェックできます。


channelの代入

package main

import (
"fmt"
)

func main() {
c := make(chan int, 10)
c2 := make(chan<- int, 10)

c2 = c

// c2に4を送信する
c2 <- 4

// c2は送信専用なのでコンパイルエラー
// fmt.Println(<-c2)

// cからを受信すると4を受け取れる
fmt.Println(<-c)
}


これだけだと、あまり大したことないですが、以下の様に関数呼び出しで使う場面もでてくるかもしれません。


送信専用のchannelの使用例

go tourの非同期的なfibonacciの例ですが

https://go-tour-jp.appspot.com/#66

このfibonacci関数内では、channelに対して送信しかしておりません。

なので


fibonacchi(送信Only)

package main

import (
"fmt"
)

func fibonacci(n int, c chan<- int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}

func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}


と書くことが出来ます。

http://play.golang.org/p/4YtIDSDGkS

これでfibonacci関数内で送信しかしないと明示でき、

間違えて受信の処理をしてしまうことを防げます。


受信専用のchannelの使用例

非同期に素数を順番にジェネレートする関数です。

呼び出した側は、受信のみにさせることが出来ます。


素因数分解のジェネレータ(受信Only)

package main

import (
"fmt"
)

func getPrimeGen() <-chan int {
var gen = make(chan int)
go func() {
var primes = make([]int, 0)
for k := 2; ; k++ {
check := true
for _, value := range primes {
if k % value == 0 {
check = false
break
}
}
if check {
primes = append(primes, k)
gen <- k
}
}
close(gen)
}()
return gen
}

func main() {
//受け取ったchannelは受信のみ
primeGen := getPrimeGen()

for i := 0; i < 10000-1; i++ {
<-primeGen
}
// 素数の10000番目を出力します。
fmt.Println(<-primeGen)

}


http://play.golang.org/p/UVk5Z5U266


まとめ

channelの送受信用のchannel型もうまく使っていきましょう。