Go並行処理テクニック!selectでchannelブロック回避する

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

Go言語switch文に似たselect文があるけど、どんな時に使うの?

この記事ではGo言語のselect文の基本を理解できます!
  • Go言語の select 文とは?
  • select 文を使用してchannelのブロック状態を回避する簡単なプログラム
  • 複数のgoroutineでselect文を使用するプログラム例

Go言語ではswitch文に似た select 文があり、select文は複数のchannelを扱う時に便利な制御構文です。

今回は、select 文の基本を理解するために簡単なプログラムを例にお伝えしていきます。

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

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

もりぴをフォローする

Go言語のselectとは?

POINT

Go言語のselect文は複数のチャネルで受信を待ってもらうことができる機能を持った制御構文です。

channelは通常、値が入っていなければ受信をブロックしますが、select 文はブロックしないで処理する時に利用します。

まずはselect文を使ってみる

それでは、Go言語のselect文について簡単なプログラムを確認していきます。

channelのブロック状態を確認する

channelに値が入っていない状態で、channelを受信するプログラムを例にブロック状態を確認していきます。

// channelのブロック状態を確認
package main

import "fmt"

func main() {
    // channelを2つ作成
    ch1 := make(chan int, 2)
    ch2 := make(chan string, 2)

    // ch2のみデータを送信
    ch2 <- "Golang"

    // ch1にデータが無い状態で受信
    a1 := <-ch1
    a2 := <-ch2

    // 値を出力
    fmt.Println(a1)
    fmt.Println(a2)
}
// 出力結果
$ go run main.go
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
        /home/morip/go/src/golangstart/channel/channel_select/main.go:19 +0xab
exit status 2

deadlockエラーになりプログラムも強制終了します。

channelはmake()関数でメモリ領域を確保しても、型の初期値までは代入されていません。 close() してはじめて型の初期値が代入されます。

channelのブロック状態を解消する簡単なプログラム例

先程のプログラムでブロック状態を解消する、簡単なプログラム例を紹介します。

もりぴ
もりぴ

ここで select 文を使っていきます。

// channelのブロック状態を解消するプログラム
package main

import "fmt"

func main() {
    // channelを2つ作成
    ch1 := make(chan int, 2)
    ch2 := make(chan string, 2)

    // ch2のみデータを送信
    ch2 <- "Golang"

    select {
    case a1 := <-ch1:
	fmt.Println("ch1からのデータを受信しました。")
	fmt.Println(a1)
    case a2 := <-ch2:
	fmt.Println("ch2からのデータを受信しました。")
	fmt.Println(a2)
    default:
	fmt.Println("データはありません。")
    }
}
// 出力結果
$ go run main.go
ch2からのデータを受信しました。
Golang

ブロック状態が解消され、プログラムが正常終了いたしました。

記述方法も switch文 と一緒ですが、select文 には switch文 と大きな違いがあります。

select文とswitch文の違い
  • switch文 上から順番に評価
  • select文 複数のchannelに値があれば、どれかひとつをランダムに評価
もりぴ
もりぴ

どういうことかと申しますと…下記のプログラムをご覧ください。

ch1 にもデータを送信してみました。

// select文のランダム評価を確認
package main

import "fmt"

func main() {
    // channelを2つ作成
    ch1 := make(chan int, 2)
    ch2 := make(chan string, 2)

    // 両方のchannelにデータを送信
    ch1 <- 123
    ch2 <- "Golang"

    select {
    case a1 := <-ch1:
	fmt.Println("ch1からのデータを受信しました。")
	fmt.Println(a1)
    case a2 := <-ch2:
	fmt.Println("ch2からのデータを受信しました。")
	fmt.Println(a2)
    default:
	fmt.Println("データはありません。")
    }
}
// 出力結果
$ go run main.go
ch2からのデータを受信しました。
Golang

または…

// 出力結果
$ go run main.go
ch1からのデータを受信しました。
123

プログラムを実行すると、出力結果の違いからわかるようにselect文はどれかひとつをランダムに評価していることが確認できます。

goroutineとchannelを使用したselect文

gorotineとchannelを使用したselect文のプログラムを確認していきます。

// goroutineでのselect文活用例
package main

import (
    "fmt"
    "time"
)

func tsetSelect1(ch chan string) {
    // 1秒待ってch3にデータ送信
    for {
	ch <- "ch3のデータです。1秒待ってデータ送信しています。"
	time.Sleep(1 * time.Second)
    }
}

func testSelect2(ch chan string) {
    // 2秒待ってch4にデータ送信
    for {
	ch <- "ch4のデータです。2秒待ってデータ送信しています。"
	time.Sleep(2 * time.Second)
    }
}

func main() {
    ch3 := make(chan string)
    ch4 := make(chan string)

    go tsetSelect1(ch3)
    go testSelect2(ch4)

    // 送信されるデータを受信して評価
    for i := 0; i < 6; i++ {
        select {
        case msg1 := <-ch3:
            fmt.Println(msg1)
        case msg2 := <-ch4:
            fmt.Println(msg2)
        }
    }
}
// 出力結果
$ go run main.go
ch4のデータです。2秒おきにデータ送信しています。
ch3のデータです。1秒おきにデータ送信しています。
ch3のデータです。1秒おきにデータ送信しています。
ch3のデータです。1秒おきにデータ送信しています。
ch4のデータです。2秒おきにデータ送信しています。
ch3のデータです。1秒おきにデータ送信しています。

goroutineとchannelをそれぞれ2つ用意して、ch3 は1秒待ってデータを送信し ch4 は2秒待ってデータを送信しています。

main() 側では受信データを6回出力するプログラムです。

もりぴ
もりぴ

select 文でランダムに評価されているのがわかります。

【まとめ】select文でchannelのブロック状態を回避する

今回はselect文でchannelのブロックを回避して、プログラムを正常終了する方法についてお伝えしてきました。

Go言語のselect文で知っておくこと!
  • select 文は複数のチャネルで受信を待ってもらうことができる機能を持った制御構文
  • select 文は複数のchannelに値があれば、どれかひとつをランダムに評価
    上から順番に評価する switch 文とは異なる

以上です。

switch文とは記述方法が一緒ですが役割が違うことを理解しておきましょう。

goroutine・channel・selectの組み合わせは、deadlockエラーを回避する有効な手段ですので押さえておきましょう。

comment

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