カテゴリ: 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
Go言語
Go言語のオブジェクト指向の特徴を完全ガイド!初心者でも理解できる他言語との違い
New2
Go言語
Go言語の条件分岐の見やすい書き方を徹底解説!初心者でもわかるif文の使い方
New3
Kotlin
Kotlinのクラス設計に役立つベストプラクティスまとめ|初心者でもわかるクラス設計の考え方
New4
Kotlin
Kotlinでアーキテクチャ設計の基本!MVC・MVP・MVVMの違いを解説
人気記事
No.1
Java&Spring記事人気No1
Go言語
Go言語の関数パラメータ!値渡しと参照渡しの違いを理解しよう
No.2
Java&Spring記事人気No2
Swift
Swift Playgroundの使い方を完全解説!初心者に最適な学習環境の始め方
No.3
Java&Spring記事人気No3
Swift
Swift開発環境の構築方法を徹底解説!Xcode・Windows・Linux対応
No.4
Java&Spring記事人気No4
Kotlin
Gradleファイル(build.gradle.kts)の書き方と役割をやさしく解説!Kotlin初心者向け完全ガイド
No.5
Java&Spring記事人気No5
Kotlin
Kotlinのインストール方法まとめ!Windows・Mac・Linux別にステップ解説
No.6
Java&Spring記事人気No6
Kotlin
Kotlinの演算子一覧と使い方!算術・比較・論理演算子の基本を解説
No.7
Java&Spring記事人気No7
Go言語
Go言語のWebアプリにおけるセキュリティベストプラクティス集
No.8
Java&Spring記事人気No8
Kotlin
Android Studioのインストール手順と初期設定を初心者向けに完全解説!