コース一覧
    Go実践:並行処理とWeb API
    並行の落とし穴

    Go実践:並行処理とWeb API

    Goの並行処理とWeb API開発を組み合わせ、実践的なサービス構築へ進むコースです。

    1
    ゴルーチンと並行処理のさわり
    01. ゴルーチン15分
    02. チャネル20分
    03. 待ち合わせ20分
    04. 並行の落とし穴20分
    05. つくる: 並行集計20分
    06. 第7章クイズ10分
    2
    Web API(net/http)
    01. サーバーを立てる20分
    02. ハンドラ関数20分
    03. JSONを返す20分
    04. ルーティング20分
    05. 一覧API20分
    06. 詳細APIと40420分
    07. 出品API20分
    08. つくる: API主要部完成25分
    09. 第8章クイズ10分
    3
    総合制作
    01. 仕様互換を仕上げる25分
    02. Node版と差し替える25分
    03. 1バイナリでデプロイ25分
    04. 自由拡張25分
    05. 完成と次のステップ20分

    並行の落とし穴

    同じ変数を同時に触ると壊れる

    Go

    total := 0
    var wg sync.WaitGroup
    
    for i := 0; i < 5000; i++ {
    	wg.Add(1)
    	go func() {
    		defer wg.Done()
    		total++
    	}()
    }
    wg.Wait()
    fmt.Println(total)

    5000 が出ると思いますが、実際は 4873 や 4991 になります。しかも毎回変わります。

    なぜ数が合わないのか

    total++ は1行ですが、機械の側では3段階に分かれています。

    プレーンテキスト

    1. total を読む
    2. 1 を足す
    3. total に書き戻す

    2つのゴルーチンが同時にこれをやると、こうなります。

    プレーンテキスト

    A: total を読む (5)
    B: total を読む (5)     ← A がまだ書き戻していない
    A: 6 を書き戻す
    B: 6 を書き戻す         ← A の分が消えた

    2回足したのに1しか増えていません。これが データ競合 です。

    見つけにくいのが厄介

    この手のバグには、たちの悪い性質があります。

    プレーンテキスト

    毎回起きるわけではない     たまに合ってしまう
    数が少ないと再現しない     負荷が高いときだけ出る
    エラーが出ない            静かに数字が狂う

    数件を並行に処理する程度では、まず起きません。 手元では正しく動き、本番で数字が合わなくなる、という現れ方をします。第6章のポインタレシーバも「静かに効かない」問題でしたが、こちらは毎回結果が違うぶん、さらに厄介です。

    Go には検出する道具があります。

    プレーンテキスト

    go run -race main.go

    -race を付けると、危ない同時アクセスを見つけて教えてくれます。数が少なくて表面化しない場合でも指摘してくれるので、並行処理を書いたら一度は通す価値があります。

    Mutex で守る

    直し方は、1人ずつしか触れないようにすることです。

    Go

    var mu sync.Mutex
    
    go func() {
    	defer wg.Done()
    	mu.Lock()
    	total++
    	mu.Unlock()
    }()

    Lock から Unlock までの間は、他のゴルーチンが待たされます。1人が終わるまで次が入れないので、さっきの割り込みが起こりません。

    Unlock の呼び忘れは、全員が待ち続けて止まる原因になります。defer mu.Unlock() と書くのが安全です。

    そもそも共有しない

    もっとよい解決があります。共有しなければ守る必要もありません。

    前のレッスンでやった形がそれです。

    Go

    results[idx] = v * v   // 添字が違うので衝突しない

    別々の場所に書けば、Mutex は要りません。待ちが発生しないぶん速くもなります。

    Go にはこういう言い回しがあります。

    解説

    メモリを共有して通信するな。通信してメモリを共有せよ。

    チャネルで値を渡す設計にすれば、そもそも同じ変数を奪い合わないという意味です。Mutex は最後の手段で、まず分けるか渡すを考えるのが Go の流儀です。

    要件

    1. 5000個のゴルーチンをそれぞれ go で走らせる
    2. sync.WaitGroup で全部の完了を待つ
    3. count++ を sync.Mutex で守る
    4. Unlock の呼び忘れが起きない書き方にする
    5. 戻り値がちょうど 5000 になる

    ヒント

    `var mu sync.Mutex` を `wg` の隣に用意します

    `count++` の前に `mu.Lock()`、その直後に `defer mu.Unlock()` を書きます

    初期コードを実行すると、5000 より少し小さい値が毎回違って返ります。それがデータ競合の症状です

    生田 陸人
    監修生田 陸人
    ゆめさくエンジニア / 現役ソフトウェアエンジニア監修者プロフィールを見る →
    編集 ゆめさく編集部·公開 2026/08/10

    関連レッスン

    • つくる: 並行集計

      第7章の知識でカテゴリ別の集計を並行に実行し、結果を安全にまとめられるようになります。

    • 第7章クイズ

      ゴルーチン、チャネル、WaitGroup、データ競合についての理解を確認します。

    • サーバーを立てる

      net/httpでHTTPサーバーを立て、リクエストに応答を返せるようになります。

    • 仕様互換を仕上げる

      ステータスコードとエラー形式を仕様に揃え、互換のあるAPIとして仕上げられるようになります。

    このレッスンに出てくる用語

    意味があいまいなまま進んだ語は、ここから読み直せます。

    • 変数データに名前をつけて参照する仕組み
    • 処理計算や代入を表す長方形
    • メモリプログラムとデータを一時保持する高速領域
    • 設計何をどう作るかを決める前工程
    • 戻り値呼び出し元への返答を表す点線矢印
    main.go
    エディタを読み込んでいます

    メモ

    並行の落とし穴

    ⌘S で保存