channelの基本|Goの並行処理でのデータ送受信を理解する!

Go言語
この記事は約8分で読めます。
Go初学者
Go初学者

Go言語のchannelの基本から学習したいです…

この記事ではGo言語のchannelの基本を理解できます!
  • channelとは?
  • channelを使ってデータの送受信をしてみる
  • channelで注意したいバッファ(容量)について

Go言語の並行処理を高度にするために必要な機能として channel があります。

channelを攻略するためには覚えることも多いですが、覚えることを分解して徐々にスキルを積み上げることで攻略できます!

今回は、その一歩としてchannelを使ってデータの送受信を理解していきましょう。

もりぴ
この記事を書いた人

XHTML1.0時代にHTML&CSSを勉強した経験あり。無趣味だった私が2020年5月からプログラミング学習を開始し現在も挫折せずに趣味で学習を楽しんでいる51歳。プログラミングの楽しさをブログを通してお伝えしていきます。

もりぴをフォローする

channelとは?

POINT

Go言語におけるchannelとは、複数のgoroutine間でデータの受け渡しをするために設計されたデータ構造です。

もりぴ
もりぴ

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

Go言語のchannelイメージ

並行処理しているgoroutineでは、channelを通してデータの送受信が行われます。

channelの宣言とメモリ容量の確保

いきなり並行処理しているプログラムにchannelを実装する前に、channelの基本的なことを理解していきましょう。

channelを宣言する

channelを使用するには、先にchannelを用意しておく必要がありがます。

まずは、channelの宣言方法を確認しましょう。

channelの宣言
  • 送受信用のchannel var channel名 chan 型
  • 受信専用のchannel var channel名 <-chan 型
  • 送信専用のchannel var channel名 chan<- 型

今回は送受信用のchannel宣言を使用していきます。

channelのメモリ容量の確保

channelの宣言だけでは nil 状態でデータを送受信できないので、make() 関数でメモリに領域を確保していきます。

変数名 = make(chan 型, バッファ)

var を省略してchannel宣言と make() 関数によるメモリ確保を一緒に定義することができます。

変数名 := make(cahn 型, バッファ)

これらを踏まえた上で、実際にプログラムで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))
}
POINT

プログラムで使用している cap() 関数はバッファサイズを出力する関数です。

// 出力結果
$ go run main.go 
現在のバッファサイズは 3
要素数は 3
1
要素数は 2
もりぴ
もりぴ

それでは、このプログラムのポイントをまとめてみます!

channelのデータ送受信のポイント
  • channelにデータを送信する チャネル名 <- 値
  • channelからデータを受信する 変数名 := <-チャネル名
  • channelからデータを取り出すと最初のデータが取り出される
  • channelからデータが取り出されると、その値は消えバッファが確保される

データの取り出しは先入れ先出し(キュー)で行われ、先に入れたデータ(今回は 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とは複数のgoroutine間でデータの受け渡しをするために設計されたデータ構造
  • channelの宣言
    1. 送受信用のchannel var channel名 chan 型
    2. 受信専用のchannel var channel名 <-chan 型
    3. 送信専用のchannel var channel名 chan<- 型
  • channelのメモリ容量の確保
    1. 変数名 = make(chan 型, バッファ)
    2. 変数名 := make(cahn 型, バッファ)
  • cap() 関数はバッファサイズを出力する関数
  • channelにデータを送受信
    1. データを送信する チャネル名 <- 値
    2. データを受信する 変数名 := <-チャネル名
  • データの取り出しは先入れ先出し(キュー)で行われる
  • channelのバッファサイズを超えるデータを送信するとdeadlock!エラーになりプログラムが終了する

以上です。

並行処理のプログラムにchannelを実装していないので、物足りなさはあるかもしれません。

しかし、ここをきちんと押さえておくことで並行処理にchannelを実装したプログラムも理解しやすくなるはずです。

特に、channelの宣言と make() 関数によるメモリの確保、データの送受信については覚えておくようにしましょう。

もりぴ
もりぴ

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

comment

タイトルとURLをコピーしました