
Go言語のchannelの基本から学習したいです…
Go言語の並行処理を高度にするために必要な機能として channel
があります。
channelを攻略するためには覚えることも多いですが、覚えることを分解して徐々にスキルを積み上げることで攻略できます!
今回は、その一歩としてchannelを使ってデータの送受信を理解していきましょう。
channelとは?
Go言語におけるchannelとは、複数のgoroutine間でデータの受け渡しをするために設計されたデータ構造です。

私の脳内イメージは、こんな感じ…

並行処理しているgoroutineでは、channelを通してデータの送受信が行われます。
channelの宣言とメモリ容量の確保
いきなり並行処理しているプログラムにchannelを実装する前に、channelの基本的なことを理解していきましょう。
channelを宣言する
channelを使用するには、先にchannelを用意しておく必要がありがます。
まずは、channelの宣言方法を確認しましょう。
今回は送受信用のchannel宣言を使用していきます。
channelのメモリ容量の確保
channelの宣言だけでは nil 状態でデータを送受信できないので、make()
関数でメモリに領域を確保していきます。
var
を省略してchannel宣言と make()
関数によるメモリ確保を一緒に定義することができます。
これらを踏まえた上で、実際にプログラムでchannelの動きを確認していきましょう。
channelの超基本的なデータ送受信を確認しよう
それでは、channelのデータ送受信をプログラムで確認していきます。
そして、バッファについても注意点があるのでプログラムで確認しながらお伝えしていきます。
channelデータ送受信をプログラムで確認する
それでは、channelの送受信をするプログラムを確認していきます。
ここでは、make()
関数でバッファサイズを 3
にしています。
// channelデータ送受信
package main
import "fmt"
func main() {
// channelの宣言(送受信:双方向)
// nil状態でこのままでは使用できない
var c1 chan int
// make関数を使用してchannelとしての機能を持たせる
// バッファサイズは「3」に指定
c1 = make(chan int, 3)
// cap関数を使用してバッファサイズを調べる
fmt.Println("現在のバッファサイズは", cap(c1))
// clにデータを送信する
c1 <- 1
c1 <- 2
c1 <- 3
// c1の要素数を調べる
fmt.Println("要素数は", len(c1))
// channelからデータを受信する(変数に格納)
i := <-c1
// c1からデータをひとつ取り出す
fmt.Println(i)
// c1の要素数を調べる
fmt.Println("要素数は", len(c1))
}
プログラムで使用している cap()
関数はバッファサイズを出力する関数です。
// 出力結果
$ go run main.go
現在のバッファサイズは 3
要素数は 3
1
要素数は 2

それでは、このプログラムのポイントをまとめてみます!
データの取り出しは先入れ先出し(キュー)で行われ、先に入れたデータ(今回は 1
)をデータが取り出され、データの取り出す順序が保証されます。
そして、データを取り出した分のバッファの空きを確保できます。

先入れ先出しは「ところてん」をイメージするとわかりやすいですよ。

channelのバッファについて
変数名 = make(chan 型, バッファ)
のバッファは、データが送信できる要素数分のメモリを先に確保するというイメージです。
先程のプログラムでは、c1 = make(chan int, 3)
バッファサイズを 3
に設定していたので要素を3個送信することができます。
channelのバッファサイズを超えたプログラム例
バッファサイズを超えた要素のデータを送信した場合どうなるのでしょうか?
// channelデータ送受信
package main
import "fmt"
func main() {
var c1 chan int
c1 = make(chan int, 3)
fmt.Println("現在のバッファサイズは", cap(c1))
// clにデータを送信する
// バッファサイズを超えるデータを送信する
c1 <- 1
c1 <- 2
c1 <- 3
c1 <- 4
fmt.Println("要素数は", len(c1))
i := <-c1
fmt.Println(i)
fmt.Println("要素数は", len(c1))
}
// 出力結果
$ go run main.go
現在のバッファサイズは 3
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/home/morip/go/src/golangstart/lesson/main.go:22 +0x167
exit status 2
deadlock!エラーが出力され、channelへのデータ送信を拒否した状態になりプログラムが終了します。
channelのバッファサイズを超えた分を解消する
先程の「ところてん」の画像を見ると、ところてんが押し出された分のスキマが空きます。

バッファもデータを取り出すと、バッファに空きができるのでdeadlock!エラー状態を回避することができます。
// channelデータ送受信
package main
import "fmt"
func main() {
var c1 chan int
c1 = make(chan int, 3)
fmt.Println("現在のバッファサイズは", cap(c1))
// clにデータを送信する
// バッファサイズを超えるデータを送信する
c1 <- 1
// channelからデータを受信してバッファの空きを確保
fmt.Println(<-c1)
fmt.Println("要素数は", len(c1))
c1 <- 2
c1 <- 3
c1 <- 4
fmt.Println("要素数は", len(c1))
i := <-c1
fmt.Println(i)
fmt.Println("要素数は", len(c1))
}
// 出力結果
$ go run main.go
現在のバッファサイズは 3
1
要素数は 0
要素数は 3
2
要素数は 2

fmt.Println(<-c1)
のように Println()
でも値を出力することができます。
【まとめ】channelのデータ送受信を理解する
今回はchannelの基本中の基本をお伝えしていきました。
それでは、まとめにはいりましょう。
以上です。
並行処理のプログラムにchannelを実装していないので、物足りなさはあるかもしれません。
しかし、ここをきちんと押さえておくことで並行処理にchannelを実装したプログラムも理解しやすくなるはずです。
特に、channelの宣言と make()
関数によるメモリの確保、データの送受信については覚えておくようにしましょう。

暗記する必要はありません。忘れたら記事を確認すればOKです!
comment