カテゴリ: Go言語 更新日: 2026/01/19

Go言語のselect文の使い方を完全ガイド!初心者でもわかる並行処理の条件分岐

Go言語のselect文を使った並行処理での条件分岐の基本
Go言語のselect文を使った並行処理での条件分岐の基本

先生と生徒の会話形式で理解しよう

生徒

「Go言語で複数の処理を同時に動かしたいときって、どうやって条件分けするんですか?」

先生

「Go言語では、select文を使えば、複数の処理を並行(同時)に実行しながら、条件分岐をすることができますよ。」

生徒

「それって難しそう…並行処理とかよくわかりません。」

先生

「大丈夫です!このあと、初心者にもわかりやすく丁寧に解説していきます。一緒に学んでいきましょう!」

1. Go言語のselect文とは?

1. Go言語のselect文とは?
1. Go言語のselect文とは?

select文は、Go言語で並行処理(goroutine)を扱うときに使う、チャネル受信に特化した条件分岐です。複数の処理が同時進行しているとき、「どのチャネルから先に届いたか」に応じて分岐します。先着順で処理を振り分けるスイッチのような役割、と覚えると理解しやすいです。

Goでは、軽量スレッドであるgoroutineが仕事を並行して進め、結果や通知はchannel(チャネル)で受け渡しします。selectはその受け取り口をひとまとめにして、最初に準備ができたチャネルの処理だけを実行します。待ち合わせの行列で、先に呼ばれた窓口に進むイメージです。

まずは雰囲気をつかむための最小サンプルです。細かな仕組みは後の章で解説しますが、「どちらか先に届いた方を表示する」動きを確認できます。


package main

import "fmt"

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() { ch1 <- "一番乗りはch1" }()
    go func() { ch2 <- "一番乗りはch2" }()

    select {
    case msg := <-ch1:
        fmt.Println(msg) // ch1が先ならこちら
    case msg := <-ch2:
        fmt.Println(msg) // ch2が先ならこちら
    }
}

このようにselectは、複数のチャネルを同時に監視し、準備できた1つだけを選んで処理します。並行処理・非同期処理で起こりがちな「どれが先に終わるか分からない」状況でも、シンプルな書き方で安全に分岐できるのが特徴です。

2. select文の基本構文

2. select文の基本構文
2. select文の基本構文

select文は、チャネルを使った送受信を条件として分岐します。各caseには「受信(x := <-ch)」または「送信(ch <- v)」のいずれかを書き、準備ができたものだけが実行されます。複数が同時に準備できていれば、その中から1つが選ばれます。どれも準備できていない場合、defaultが無ければ待機、defaultがあればすぐにそちらが走ります。


select {
case v := <-ch1:
    // ch1から受信できたときの処理(vを使う)
case ch2 <- 10:
    // ch2へ送信できたときの処理(10を送る)
default:
    // どのチャネルも準備できていないとき(省略可)
}

最小の具体例で、送信側のcasedefaultの動きを確認してみましょう。バッファ付きチャネルを使うと、今この瞬間に送れるかどうかで分岐できます。


package main

import "fmt"

func main() {
    ch := make(chan int, 1) // 1個だけ入るバッファ
    select {
    case ch <- 100:
        fmt.Println("送信できました")
    default:
        fmt.Println("今は送信できません")
    }

    select {
    case v := <-ch:
        fmt.Println("受信:", v)
    default:
        fmt.Println("今は受信できません")
    }
}

このサンプルでは、最初のselect100の送信に成功し、次のselectでその値を受信します。selectはこのように「今すぐ進める処理を安全に選ぶ」ための基本構文として使います。

3. 実際にselect文を使ったサンプルを見てみよう

3. 実際にselect文を使ったサンプルを見てみよう
3. 実際にselect文を使ったサンプルを見てみよう

実際に、2つのチャネルを使って、どちらか先にデータが来た方の処理をする例を見てみましょう。


package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(2 * time.Second)
        ch1 <- "チャネル1からデータが届きました"
    }()

    go func() {
        time.Sleep(1 * time.Second)
        ch2 <- "チャネル2からデータが届きました"
    }()

    select {
    case msg1 := <-ch1:
        fmt.Println(msg1)
    case msg2 := <-ch2:
        fmt.Println(msg2)
    }
}

このコードでは、ch1ch2という2つのチャネルがあります。1秒後にch2から、2秒後にch1からデータが届きます。

select文は、先に届いたch2のデータを受け取って、その処理を実行します。

実行結果:


チャネル2からデータが届きました

Go言語を基礎からスッキリ学びたい人や、 文法だけでなく「実用的な使い方」まで押さえたい人には、 定番の入門書がこちらです。

基礎からわかるGo言語をAmazonで見る

※ Amazon広告リンク

4. default文で待たずに処理する

4. default文で待たずに処理する
4. default文で待たずに処理する

select文にはdefault節を使うこともできます。

これは、どのチャネルにもデータが来ていないときに、すぐに別の処理をさせたい場合に使います。


select {
case msg := <-ch1:
    fmt.Println(msg)
default:
    fmt.Println("まだデータが来ていません")
}

この例では、チャネルch1からデータが来ていなければ、すぐに"まだデータが来ていません"と表示されます。

5. select文を使うときのポイント

5. select文を使うときのポイント
5. select文を使うときのポイント
  • 1つでもcaseのチャネルにデータがあれば、select文はすぐに動く
  • すべてのチャネルにデータがない場合は、select文は待機状態になる
  • default節があると、待たずにすぐに実行される

この仕組みを使えば、複数の並行処理の中から、「一番早く終わった処理」を優先して動かすことができるのです。

6. 実生活で例えるなら?

6. 実生活で例えるなら?
6. 実生活で例えるなら?

イメージしやすくするために、select文を「電話の呼び出し」に例えてみましょう。

あなたが2つの電話を持っていて、どちらかが先に鳴ったら、そちらに出る…というのがselectの動きです。

どちらも鳴らなければ待ち続けるし、「電話が鳴らなかったらとりあえずテレビを見る」と決めているなら、それがdefaultです。

7. select文はどんな場面で役立つの?

7. select文はどんな場面で役立つの?
7. select文はどんな場面で役立つの?

ネットワーク通信タイマー処理ユーザー入力の待ち受けなど、さまざまな処理を同時に扱う場面でselect文は大活躍します。

たとえば、「3秒以内にサーバーから返事がなければタイムアウトする」という処理も、selectを使えば簡単に書けるのです。

まとめ

まとめ
まとめ

学んだポイントの総整理

ここまでで、Go言語のselect文が「複数のチャネルを同時に見張り、最初に準備できたものだけを安全に処理する仕組み」であることを確認できました。基本構文はとても素直で、caseに書けるのは送受信だけ、同時に複数が準備できた場合はその中からひとつだけが選ばれます。どれも準備できていないなら待機、ただしdefaultがあればすぐに先へ進めます。これだけで、並行処理の「待つ/進める」の判断が読みやすい形で記述できます。

また、goroutineで走る処理の結果や通知をchannel経由で受け取り、selectで先着順に振り分けるという作法を覚えると、タイマー、ユーザー入力、ネットワークの応答など「どちらが先かわからない出来事」をスムーズに扱えるようになります。特に、一定時間だけ待って諦める「タイムアウト」や、今は待たずに別処理に移る「default分岐」は実装の現場でとても重宝します。

書き方の型を身につける(最小の型)

まずは最小の型を手に馴染ませましょう。caseは受信か送信、defaultは「今すぐ動けるものが無い」時の逃げ道です。短いサンプルで、待つ/待たないの感覚を掴むのが近道です。


package main

import "fmt"

func main() {
    done := make(chan struct{})
    msg  := make(chan string, 1)

    // いま送れるなら送る(バッファに空きがあると送信可能)
    select {
    case msg <- "すぐに送れました":
        fmt.Println("送信成功")
    default:
        fmt.Println("まだ送れません")
    }

    // いま受け取れるなら受信する(値が入っていれば即時)
    select {
    case m := <-msg:
        fmt.Println("受信:", m)
    case <-done:
        fmt.Println("終了します")
    default:
        fmt.Println("何も準備できていません")
    }
}

この型を出発点に、必要に応じてチャネルの数を増やしたり、分岐後の処理を関数に切り出したりすれば、規模が大きくなっても読みやすさを保ちやすくなります。

タイムアウトとリトライの基本パターン

現場で頻出するのが「一定時間だけ待ってから諦める」という分岐です。time.Afterが返すチャネルをselectに混ぜるだけで、難しい仕組みを用意しなくても明快に表現できます。下は簡単なリトライつきの型です。


package main

import (
    "fmt"
    "time"
)

func request() <-chan string {
    ch := make(chan string, 1)
    go func() {
        // 実際はHTTPやDBなどの処理を想定
        time.Sleep(1500 * time.Millisecond)
        ch <- "応答OK"
    }()
    return ch
}

func main() {
    const maxRetry = 2
    for attempt := 1; attempt <= maxRetry; attempt++ {
        select {
        case res := <-request():
            fmt.Println("成功:", res)
            return
        case <-time.After(1 * time.Second):
            fmt.Println("タイムアウト、やり直します(試行", attempt, ")")
        }
    }
    fmt.Println("失敗:規定回数内に応答がありませんでした")
}

このパターンを覚えておくと、基礎的なネットワーク待ち・ファイルI/Oの待ち・外部コマンドの待ちなどにそのまま応用できます。実務では、試行回数や待ち時間を定数化しておくと、テストや運用の調整が楽になります。

イベントループの型(繰り返し+停止シグナル)

複数の入力源を継続的に回し続けるときは、for + selectの組み合わせが定番です。停止はdoneチャネルを閉じるだけで簡潔に表現できます。下の例では、メッセージ処理・秒ごとの心拍(ハートビート)・停止命令の三者を同時に扱っています。


package main

import (
    "fmt"
    "time"
)

func main() {
    msgs := make(chan string)
    done := make(chan struct{})

    // 入力源(例:別ゴルーチンで送られてくるメッセージ)
    go func() {
        msgs <- "こんにちは"
        time.Sleep(300 * time.Millisecond)
        msgs <- "処理中です"
        time.Sleep(300 * time.Millisecond)
        close(done) // 終了シグナル
    }()

    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case m := <-msgs:
            fmt.Println("受信:", m)
        case <-ticker.C:
            fmt.Println("ハートビート")
        case <-done:
            fmt.Println("終了します")
            return
        }
    }
}

このように書いておけば、どの入力が先でも安全に処理され、終了も素直に扱えます。後から処理を増やしてもcaseを足していくだけなので、構造が崩れにくいのも利点です。

読みやすさを保つ小さなコツ

分岐が増えるほど読み手にとっての負担は大きくなります。selectの各caseが長くなってきたら、関数に切り出すログの書式を揃えるチャネル名を用途で命名するといった地味な工夫が効きます。また、defaultは便利ですが、頻用しすぎると「常にdefaultばかり選ばれて忙しく回り続ける」状態にもなり得ます。必要な場面だけに絞ると、CPUの無駄な消費を抑えられます。

練習課題(手を動かして理解を固める)

次の小さな課題を手元で試すと、理解がぐっと定着します。二つのチャネルの先着勝ちを表示する(基本)。time.Afterで1秒タイムアウトを入れてみる(遅い側を諦める)。for+selectで10回だけメッセージを処理し、終わったらdoneで停止する(イベントループ)。コードを短く保ちながら、名前やログの書式で「何を待っているか」が読み取れるように工夫してみましょう。

先生と生徒の振り返り会話

生徒

selectって、たくさんの処理を同時に待つための『受付』みたいな感じなんですね。早く終わった受付だけ対応する、と。」

先生

「その通り。caseは受付窓口、チャネルは呼び出しベル。鳴った窓口だけ応対するイメージです。鳴らないならdefaultで次へ進めば、処理が止まらない。」

生徒

time.Afterを混ぜれば、待ちすぎないでタイムアウトも書ける、と。実際のアプリでも使えそうです。」

先生

「よく使いますよ。ネットワークの応答待ち、バッチ処理の監視、UIの入力待ち……どれもselectの得意分野です。for+selectでループ化して、doneで止める型も覚えておくと、設計が安定します。」

生徒

「各caseが長くなって読みにくかったら、関数に切ればよいんですね。ログの形も揃えておけば、あとから読んでも流れが追いやすそう。」

先生

「うん。あとはdefaultの使いすぎに注意。常にdefaultが選ばれると忙しく回ってしまう。必要な場面だけにして、待つべきところはきちんと待つ。これがコツです。」

生徒

「わかりました。まずは先着勝ち、タイムアウト、イベントループの三つを練習して、selectの感覚を掴みます!」

この記事を読んだ人からの質問

この記事を読んだ人からの質問
この記事を読んだ人からの質問

プログラミング初心者からのよくある疑問/質問を解決します

Go言語のselect文って何のために使う機能ですか?初心者でも理解できますか?

Go言語のselect文は、複数の処理を同時に実行しながら、どのチャネルから先にデータが届いたかを判断して条件分岐するための機能です。チャネルとゴルーチンを使うことで同時処理ができ、その中で先に完了した処理を選んで動かせるので、ネットワーク通信やタイマー処理など多くの場面で便利に使われています。
関連記事:
カテゴリの一覧へ
新着記事
New1
Go言語
Go言語の短絡評価(ショートサーキット)を使った条件式の工夫をやさしく解説!初心者でも理解できる基本知識
New2
Go言語
Go言語の構造体の初期化パターンとコンストラクタ的関数の書き方を徹底解説!初心者でもわかる基本と実用例
New3
Kotlin
Kotlinの例外処理とキャンセルの連携を完全ガイド!初心者でもわかるCoroutineExceptionHandlerの使い方
New4
Go言語
Go言語のクロージャとは?関数内関数の活用例と仕組み
人気記事
No.1
Java&Spring記事人気No1
Kotlin
KotlinのChannelでデータをやり取りする方法を完全ガイド!初心者にもわかる非同期通信の基本
No.2
Java&Spring記事人気No2
Go言語
Go言語でのDB接続情報を環境変数で管理する方法|初心者でも安全に設定
No.3
Java&Spring記事人気No3
Kotlin
Android Studioのインストール手順と初期設定を初心者向けに完全解説!
No.4
Java&Spring記事人気No4
Kotlin
Kotlinのビルド設定エラーと解決法まとめ!初心者向けGradleトラブル対処ガイド
No.5
Java&Spring記事人気No5
Kotlin
Gradleファイル(build.gradle.kts)の書き方と役割をやさしく解説!Kotlin初心者向け完全ガイド
No.6
Java&Spring記事人気No6
Kotlin
Kotlinでテキスト表示・編集!初心者でもわかるTextViewとEditTextの使い方
No.7
Java&Spring記事人気No7
Kotlin
Kotlin DSLとGroovy DSLの違いをやさしく解説!初心者でもわかるGradleスクリプトの使い分け
No.8
Java&Spring記事人気No8
Swift
Swift Playgroundの使い方を完全解説!初心者に最適な学習環境の始め方

💻 作業効率アップに

ノートPCを縦置きしてデスクを広く。
省スペースで片づく定番スタンド

UGREEN 縦型スタンドをAmazonで見る

※ Amazon広告リンク