LoginSignup
34
28

More than 3 years have passed since last update.

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

Last updated at Posted at 2015-08-23

初めに

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)

}

まとめ

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

34
28
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
34
28