Go言語

【Go言語】「バッファありチャネル(channel)」を理解したいんじゃ!!

チャネル(channel)はゴルーチン(goroutine)間で値を受け渡しすることができる通信の仕組みで、チャネルは要素型と呼ばれる特定の型の値のみ送信できる。

今回は、「バッファなしチャネル」と異なる点を中心に「バッファありチャネル」についてまとめていく。

チャネルの基本的なことは「【Go言語】「バッファなしチャネル(channel)」を理解したいんじゃ!!」を参照。

 

バッファありチャネル(channel)とは?

バッファありチャネルは、要素のキューを持っている。キューのサイズはチャネルを生成するときにmake に渡される容量の引数によって決まる。

チャネルに送信するとき、キューの最後に値を追加し、受信するときはキューの先頭から値を取り除く。

バッファありチャネルのイメージ

バッファありチャネルの宣言、初期化

「バッファありチャネル」の場合、make の2つ目の引数にバッファのサイズを渡す。

例えば、3つのint値を保持できるバッファありチャネルの場合

var ch chan int
ch = make(chan int, 3)

// 関数内であれば、 := を使ってもOK
ch := make(chan int, 3)

チャネルのブロック

バッファありチャネルの場合、

ポイント

  • チャネルが「一杯」なら、送信操作は、別のゴルーチンによる受信操作によって「空き」ができるまで待たされる(ブロック)
    → チャネルに「空き」があれば、送信操作は待たされない
  • チャネルが「空」なら、受信操作は別のゴルーチンが値を送信するまで待たされる(ブロック)

簡単なサンプルを書いてみる。

3つのint値を保持できるバッファありチャネルに対して

  • 5つのint値を送信する無名ゴルーチン
  • 受信するメインゴルーチン

を用意。

実行結果と合わせてみるとブロックの様子がわかりやすいはず。

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int, 3)

	go func() {
		defer close(ch)
		for i := 0; i < 5; i++ {
			ch <- i
			fmt.Printf("sent %d\n", i)
		}
	}()

	time.Sleep(3 * time.Second) // 出力をわかりやすくすため、スリープ
	for v := range ch {
		fmt.Printf("received value: %d\n", v)
		time.Sleep(1 * time.Second) // 出力をわかりやすくすため、スリープ
	}
}

この実行結果は、

$ go run main.go
sent 0
sent 1
sent 2             // ← ここでチャネルは一杯。チャネルが一杯になるまでは、受信操作が実行されなくても送信できる
received value: 0  // ← 1つ受信した(取り出した)ので、チャネルに空きができる
sent 3             // ← チャネルに空きができたので、送信。そして、また一杯になる
received value: 1  // ← 1つ受信した(取り出した)ので、チャネルに空きができる
sent 4             // ← チャネルに空きができたので、送信
received value: 2
received value: 3
received value: 4

実行結果のコメントに書いたように、チャネルに空きがあるうちは、待たされることなく送信でき、チャネルが一杯になったら待機しているのがわかる。

バッファありチャネル まとめ

まとめ

  • make の第二引数にバッファサイズを指定して生成
  • チャネルが「一杯」なら、送信操作は、別のゴルーチンによる受信操作によって「空き」ができるまで待たされる
  • チャネルに「空き」があるなら、送信操作は待たされることなく実行される
  • チャネルが「空」なら、受信操作は別のゴルーチンが値を送信するまで待たされる

 

参考図書

Tour of Goを終えた人にオススメです!!

-Go言語

© 2021 フリエン生活 Powered by AFFINGER5