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

Go言語の並行処理で発生するトラブル事例と解決法まとめ

Go言語の並行処理で発生するトラブル事例と解決法まとめ
Go言語の並行処理で発生するトラブル事例と解決法まとめ

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

生徒

「Goで複数のgoroutineを動かすと、どんなトラブルが起きるんですか?」

先生

「代表的なのは、競合状態やデッドロックです。競合状態は複数のgoroutineが同じ変数を同時に操作するときに起こります。デッドロックは、goroutineが互いに待ち状態になり処理が止まってしまうことです。」

生徒

「それを防ぐにはどうすればいいですか?」

先生

「channelやsyncを適切に使うこと、そして-raceオプションを使ったデバッグが有効です。順番に具体例を見ていきましょう。」

1. 競合状態(Race Condition)と解決法

1. 競合状態(Race Condition)と解決法
1. 競合状態(Race Condition)と解決法

競合状態は、複数のgoroutineが同じメモリを同時に書き換えるときに発生します。これにより意図しない結果が生じることがあります。Goではsync.Mutexやchannelを使って解決できます。

2. 競合状態のサンプルと-raceオプション

2. 競合状態のサンプルと-raceオプション
2. 競合状態のサンプルと-raceオプション

package main

import (
    "fmt"
    "sync"
)

func main() {
    counter := 0
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            counter++ // 競合状態発生
            wg.Done()
        }()
    }

    wg.Wait()
    fmt.Println("Counter:", counter)
}

このコードをgo run -race main.goで実行すると、競合状態が検出されます。Mutexを使うことで安全に更新可能です。

3. Mutexで競合状態を防ぐ方法

3. Mutexで競合状態を防ぐ方法
3. Mutexで競合状態を防ぐ方法

var mu sync.Mutex

go func() {
    mu.Lock()
    counter++
    mu.Unlock()
    wg.Done()
}()

Mutexでロックとアンロックを行うことで、同時アクセスによるデータ破損を防ぎます。

4. デッドロックとその解決法

4. デッドロックとその解決法
4. デッドロックとその解決法

デッドロックは、複数のgoroutineが互いに相手の処理完了を待ち続けてしまう状態です。特にchannelを使った送受信で発生しやすく、送信側と受信側が揃わない場合に起こります。

5. デッドロックの例と回避

5. デッドロックの例と回避
5. デッドロックの例と回避

ch := make(chan int)

go func() {
    ch <- 1 // 受信側がないとデッドロック
}()

// main goroutineで受信
fmt.Println(<-ch)

受信がないまま送信すると、処理が止まります。必ず送信と受信が揃うようにgoroutineを設計することが重要です。

6. channelの閉鎖忘れによる問題

6. channelの閉鎖忘れによる問題
6. channelの閉鎖忘れによる問題

channelは使い終わったらclose()で閉じるのが安全です。閉じないと、受信側が永遠に待ってしまうことがあります。


ch := make(chan int)

go func() {
    ch <- 100
    close(ch) // 忘れずに閉じる
}()

for v := range ch {
    fmt.Println(v)
}

7. ゴルーチンリークの防止

7. ゴルーチンリークの防止
7. ゴルーチンリークの防止

goroutineリークとは、終了すべきgoroutineがいつまでも動き続けることです。contextパッケージを使ってタイムアウトやキャンセルを設定することで防げます。


ctx, cancel := context.WithCancel(context.Background())

go func() {
    select {
    case <-ctx.Done():
        return
    }
}()

cancel() // goroutineを終了

8. トラブルを防ぐ設計のポイント

8. トラブルを防ぐ設計のポイント
8. トラブルを防ぐ設計のポイント
  • 共有変数への同時アクセスにはsync.Mutexやsync.RWMutexを使う
  • データの受け渡しはchannelを使い、送受信の順序を揃える
  • goroutineの終了待ちはsync.WaitGroupで管理する
  • goroutineリーク防止のためにcontextでキャンセルやタイムアウトを設定する
  • 競合状態のチェックには-raceオプションを活用する

これらを組み合わせることで、Goの並行処理で発生するトラブルを未然に防ぎ、安全なプログラム設計が可能です。

カテゴリの一覧へ
新着記事
New1
Kotlin
Kotlinの構文ルールまとめ!インデント・セミコロンなど初心者が最初に覚えるべきポイント
New2
Kotlin
Kotlinの関数ドキュメンテーションコメント(KDoc)の書き方を徹底解説!初心者でもわかる丁寧なガイド
New3
Kotlin
KotlinでHello Worldを表示するには?最初の1行を実行してみよう
New4
Go言語
Go言語の依存関係管理を始めよう!go modの基本設定と導入手順
人気記事
No.1
Java&Spring記事人気No1
Kotlin
Gradleファイル(build.gradle.kts)の書き方と役割をやさしく解説!Kotlin初心者向け完全ガイド
No.2
Java&Spring記事人気No2
Go言語
Go言語の関数パラメータ!値渡しと参照渡しの違いを理解しよう
No.3
Java&Spring記事人気No3
Kotlin
KotlinのAPI通信でGETリクエストを送る方法!初心者向け徹底ガイド
No.4
Java&Spring記事人気No4
Swift
SwiftのURLSessionでのネットワークエラー対策!再試行とタイムアウトを初心者向けに解説
No.5
Java&Spring記事人気No5
Go言語
Go言語のテストでタイムアウト・並行処理を扱うポイント
No.6
Java&Spring記事人気No6
Kotlin
Android Studioのインストール手順と初期設定を初心者向けに完全解説!
No.7
Java&Spring記事人気No7
Go言語
Go言語のWebアプリにおけるセキュリティベストプラクティス集
No.8
Java&Spring記事人気No8
Swift
Swift JSONデコード失敗の原因と対処|DecodingError徹底解説