カテゴリ: Go言語 更新日: 2026/03/10

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つの処理selectで受け取る最小サンプルから始めます。動きはシンプルで、ch1ch2のどちらか早く届いた方だけを表示します。コメントに沿って流れを追ってみてください。


package main

import (
    "fmt"
    "time"
)

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

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

    // 1秒後にch2へ通知(こちらが先に準備できる想定)
    go func() {
        time.Sleep(1 * time.Second)
        ch2 <- "チャネル2からデータが届きました"
    }()

    // 先に準備できたチャネルだけが選ばれる
    select {
    case msg1 := <-ch1:
        fmt.Println(msg1)
    case msg2 := <-ch2:
        fmt.Println(msg2)
    }
}

このコードでは、ch2が1秒で先に準備されるため、そのメッセージが表示されます。selectは「最初に届いた知らせだけ処理する」という性質を持つため、同時に複数は実行しません。

次は、「一定時間で諦める」動きを加えた実用寄りの例です。time.Afterは指定時間経過後に自動で値を送る読み取り専用チャネルを返してくれるので、selectに混ぜるだけで簡単にタイムアウトが書けます。


package main

import (
    "fmt"
    "time"
)

func main() {
    slow := make(chan string)

    // わざと時間のかかる処理を表現(2秒待ってから通知)
    go func() {
        time.Sleep(2 * time.Second)
        slow <- "ゆっくり処理が終わりました"
    }()

    // 1秒だけ待って、来なければタイムアウト扱い
    select {
    case msg := <-slow:
        fmt.Println(msg)
    case <-time.After(1 * time.Second):
        fmt.Println("タイムアウト:結果が間に合いませんでした")
    }
}

このサンプルでは、重い処理からの通知が2秒後で遅いため、1秒の待ち時間を過ぎたtime.After側が先に準備でき、タイムアウトのメッセージが表示されます。selectにより、最も早く応答したチャネルを自然な書き方で選び取れることが実感できるはずです。

実行結果の例:


チャネル2からデータが届きました
(タイムアウト例では)
タイムアウト:結果が間に合いませんでした

「どれが早いか分からない」「一定時間で待つのをやめたい」といった初心者がつまずきやすい場面でも、selectなら読みやすく安全に書けます。まずは上の2例を手元で動かして、振る舞いを体で覚えてみてください。

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

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

※ Amazon広告リンク

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

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

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

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

Go言語のselect文は、複数の処理を同時に実行しながら、どのチャネルから先にデータが届いたかを判断して条件分岐するための機能です。チャネルとゴルーチンを使うことで同時処理ができ、その中で先に完了した処理を選んで動かせるので、ネットワーク通信やタイマー処理など多くの場面で便利に使われています。
関連セミナーのご案内

【超入門】ゼロから始めるGo言語プログラミング:最速で「動くアプリ」を作るマンツーマン指導

「プログラミングの仕組み」が根本からわかる。Go言語でバックエンド開発の第一歩を。

本講座を受講することで、単なる文法の暗記ではなく、「プログラムがコンピュータの中でどう動いているか」という本質的な理解につながります。シンプルながら強力なGo言語(Golang)を通じて、現代のバックエンドエンジニアに求められる基礎体力を最短距離で身につけます。

具体的な開発内容と環境

【つくるもの】
ターミナル(黒い画面)上で動作する「対話型計算プログラム」や、データを整理して表示する「ミニ・ツール」をゼロから作成します。自分の書いたコードが形になる感動を体験してください。

【開発環境】
プロの現場でシェアNo.1のVisual Studio Code (VS Code)を使用します。インストールから日本語化、Go言語用の拡張機能設定まで、現場基準の環境を一緒に構築します。

この60分で得られる3つの理解

1. 環境構築の完全な理解

「なぜ動くのか」という設定の仕組みを理解し、今後の独学で詰まらない土台を作ります。

2. Go言語の基本構造(変数・型)

データの種類やメモリの概念など、他言語にも通じるプログラミングの本質を学びます。

3. 読みやすいコードの書き方

ただ動くだけでなく、誰が見ても分かりやすい「綺麗なコード」を書くための考え方を伝授します。

※本講座は、将来的にバックエンドエンジニアクラウドインフラに興味がある未経験者のためのエントリー講座です。マンツーマン形式により、あなたの理解度に合わせて進行します。

セミナー画像

初めてのGo言語を一緒に学びましょう!

カテゴリの一覧へ
新着記事
New1
Go言語
Go言語のwhile的なforループの使い方!条件式ループの基本を解説
New2
Go言語
Go言語プログラムの実行方法まとめ!VSCode・ターミナルでの実行手順を解説
New3
Swift
Swift意味とは?プログラミング言語・金融・鳥の違いを徹底解説
New4
Swift
Swift 戻り値の扱い方と複数戻り値の返し方|初心者でも分かる関数の基本
人気記事
No.1
Java&Spring記事人気No1
Go言語
Go言語でリダイレクト処理を行う方法(http.Redirect)を初心者向けに解説
No.2
Java&Spring記事人気No2
Swift
Swift開発環境の構築方法を徹底解説!Xcode・Windows・Linux対応
No.3
Java&Spring記事人気No3
Kotlin
Android Studioのインストール手順と初期設定を初心者向けに完全解説!
No.4
Java&Spring記事人気No4
Kotlin
Gradleファイル(build.gradle.kts)の書き方と役割をやさしく解説!Kotlin初心者向け完全ガイド
No.5
Java&Spring記事人気No5
Go言語
Go言語のgo.modファイル完全ガイド!初心者でもわかる仕組みと書き方
No.6
Java&Spring記事人気No6
Swift
Swift Playgroundの使い方を完全解説!初心者に最適な学習環境の始め方
No.7
Java&Spring記事人気No7
Kotlin
Kotlinの演算子一覧と使い方!算術・比較・論理演算子の基本を解説
No.8
Java&Spring記事人気No8
Go言語
Go言語で条件式を1行で書くコツ!三項演算子の代替と短縮記法