
AIに非同期・並行処理を書かせる|競合とデッドロックの確認
「並列で処理して速くして」とAIに頼んだら、数十行できれいなコードが返ってきた。動かしてみると、ちゃんと速い。テストも通る。——ここまでは、たぶん今日もどこかで起きています。
そして問題が出るのは、たいてい数週間あとです。しかも「たまに」しか出ない。再現しようとしても再現しない。ログを見ても、どの処理も成功したことになっている。
このしんどさは、あなたの確認が甘かったから起きるのではありません。並行処理の不具合は、順番に動かしているあいだは絶対に姿を見せないという性質を持っているだけです。つまり、普段のやり方で見つからないのは当たり前なのです。この記事では、そこをAIとどう分担するかを整理します。
結論:AIに非同期・並行処理を書かせるときの要点は3つです。①コードを頼む前に「同時に来るもの」を3行書く——何が共有されるか/同時に起こりうる操作は何か/絶対に崩したくない条件は何か。②「動くコードを書いて」ではなく「このコードが壊れる筋書きを、時系列で書いて」と頼む。AIは正しい実装より、壊れ方の説明のほうが得意です。③直す前に、壊れることを確かめる仕掛けを1つだけ入れる——競合検出のオプション、同時実行の再現テスト、DBの一意制約のどれか1つで十分です。
AIが苦手なのは、あなたのシステムで「何が同時に起こるか」を知ることです。そこだけこちらが渡せば、あとはかなり頼れます。
全部を今日やる必要はありません。まずは①の3行からで大丈夫です。
何が起きているのか——順番に動かすと、絶対に出てこない

まず、なぜここだけ勝手が違うのかを共有させてください。理由は3つあります。
1つ目は、不具合が「タイミング」でしか現れないこと。図の左と中央の違いです。ひとりずつ順番に棚の札を取りに来るなら、何も起きません。ところが二人が同じ瞬間に同じ1枚へ手を伸ばすと、両方が「取れた」と思ってしまう。この、同じものに同時に触ったせいで結果が変わってしまう状態を競合状態(レースコンディション)と呼びます。手元での実行は、ほぼ必ず順番どおりに動きます。だから出ません。出るのは、アクセスが増えた日だけです。
2つ目は、お互いの完了を待ち合うと止まってしまうこと。図の右です。二人がそれぞれ別のものを確保したまま、相手が持っているものを待つ。どちらも譲らないので、永久に進みません。これがデッドロックです。厄介なのは、エラーにならないところ。落ちてくれれば気づけるのに、ただ静かに止まるので、監視にも「処理中」としか出ません。
3つ目は、AIがこの2つを「頼まれていない」こと。ここが本題です。「並列で速くして」と頼めば、AIは並列にします。同時アクセスを想定してほしいとは、こちらが一言も言っていないのです。AIは目の前のコードしか見ていないので、そのメソッドが1日1回のバッチから呼ばれるのか、秒間数百のリクエストから呼ばれるのかを知りません。知らないまま、いちばん素直な——つまり同時実行を考えない——コードを書きます。手を抜いたわけではなく、判断材料を渡していなかっただけです。
この3つが重なると、「レビューも通り、テストも通り、本番で月に一度だけおかしくなる」という、いちばん追いにくい状態ができあがります。逆に言えば、「何が同時に来るか」を先に言葉にして渡すだけで、かなりの部分は入口で防げるということでもあります。
具体例——AIの非同期コードが、こう抜ける
現場で実際に見かけやすいのは、次のような形です。一つでも心当たりがあれば、この記事はそのまま使えます。
- 「確認してから書き込む」のあいだに割り込まれる:いちばん多い型です。「在庫があるか確認する→あったら1つ減らす」「このメールアドレスが未登録か確認する→なければ登録する」。この確認と実行のあいだに、もう一方の処理が同じ確認を済ませてしまうと、両方が通ります。在庫がマイナスになる、同じ人が二重に登録される、といった形で現れます。
awaitをまたいだ瞬間に、状態が変わっている:JavaScriptやPythonのように、1本の流れの中で待ち合わせる書き方でも起こります。メモリが同時に書き換わることはなくても、待っているあいだに別の処理が進むので、待つ前に確認した内容が、待ったあとにはもう古いことがあります。「シングルスレッドだから安全」は、ここには当てはまりません。- 使い回してはいけないものを共有している:接続やクライアントの中には、複数の処理から同時に使うことを想定していないものがあります。AIは「使い回したほうが効率的です」と、迷わず共有する形で書いてくることがあります。動きはします。混ざるまでは。
- 並列にしすぎて、別のところが詰まる:一覧の全件を一斉に投げる書き方です。DBの接続が足りなくなる、外部APIのレート制限に当たる、メモリを使い切る。速くするつもりが、全体としては遅くなったり落ちたりします。とくにAI APIを叩く処理では上限に当たりやすいので、AI APIの料金とレート制限を見積もるもあわせて見ておくと安心です。
- リトライが二重実行になる:タイムアウトしたので、もう一度送る。ところが最初の処理は実は成功していて、返事が遅かっただけだった。結果、2件登録される。AIが書くリトライ処理は、ここまで面倒を見ていないことが多いです。
- ロックを取る順番がそろっていない:ある処理はAを取ってからB、別の処理はBを取ってからA。この2つが同時に走ると、図の右の状態になります。ファイルごとにAIへ書かせていると、順番をそろえるという発想がそもそも共有されません。
どれも、注意力の問題ではありません。これらは「動いてしまう」ことが特徴なので、動かして確かめるという普段のやり方では見つからない、というだけです。見つけにくいものを相手にしている——そう捉えるほうが、実態に近いと思います。
影響——出るのは、いちばん忙しい日
並行処理の不具合が少し特別なのは、出るタイミングを選べないところです。
- アクセスが多い日にだけ出る:同時に来る回数が増えるほど、重なる確率が上がります。つまり、セール当日、月末の締め、キャンペーンの初日——いちばん止めたくない日に顔を出しやすい。
- 再現しようとすると再現しない:報告を受けて手元で試すと、何事もなく動きます。調査が「再現できませんでした」で止まりがちで、そのぶん残り続けます。
- データのほうが先に壊れる:処理が落ちるのではなく、結果だけが静かにおかしくなることがあります。在庫の数、集計値、二重登録。気づくのは、コードではなく数字を見ている人からの問い合わせだったりします。
- たまに落ちるテストとして現れることもある:CIで時々だけ赤くなるテストの中には、この種の問題が混ざっていることがあります。切り分け方はたまに落ちるテスト(Flaky)の原因をAIに切り分けさせる手順にまとめてあります。
ただ、ここは強調しておきたいのですが、この種の抜けは、書く前なら本当に安く塞げます。あとから探すのは大変でも、先に「何が同時に来るか」を問うのは1分です。いま実装の手前でこの記事を読んでいるなら、それはいちばん安いタイミングにいるということです。
明日からやること(3ステップ)
大がかりな設計変更は要りません。この順番で、今日から小さく始められます。
1. AIに頼む前に、自分で3行書く
いきなり「並列にして」と頼まないほうが、返ってくるものの質が変わります。先に、次の3つを1行ずつ書きます。
①同時に触られるもの(どのデータ・どの行・どのファイル・どの外部サービス)
②同時に起こりうる操作(誰が・どこから・どれくらいの頻度で呼ぶか)
③崩したくない条件(在庫は0未満にならない/同じメールアドレスは1件だけ/通知は1回だけ)
例:
- 同時に触られるのは、商品テーブルの在庫数の行
- 注文APIから、ピーク時に秒あたり数十件。管理画面の在庫調整からも同じ行を更新する
- 在庫数は0未満にならない。1つの注文で2回減らさない
書いてみると分かるのですが、難しいのは③です。ここが埋まらないまま「速くして」と頼むと、AIは速さだけを最適化します。③は、AIが守るべき線をこちらから示す唯一の行だと思ってください。この3行が、そのままAIへ渡す素材になります。前提や制約の渡し方そのものはAIにコードを書かせる前に渡す前提と制約|伝え方の型と同じ考え方です。
2. 「壊れる筋書き」を時系列で書かせる
3行を渡したうえで、実装ではなく、壊れ方を先に聞きます。これはAIがかなり得意な仕事です。
そのまま使える頼み方の例:
以下のコードと前提について、修正案は出さなくて結構です。 このコードが同時に2回以上呼ばれたときに、結果がおかしくなる筋書きを挙げてください。それぞれ、処理Aと処理Bがどの順番で進むと壊れるのかを、時系列の表で示してください。とくに次の3点を名指しで見てください。
1. 確認してから書き込むまでのあいだに、割り込まれる余地はないか
2. 待ち合わせ(await・非同期呼び出し)をまたいだ前後で、前提が変わっていないか
3. 複数のロックや資源を取る順番が、呼び出し経路によって食い違っていないか
あわせて、リトライやタイムアウトで同じ処理が2回実行された場合に何が起きるかも書いてください。
前提:
(ここに3行)
コード:
(ここに対象のコード)
ポイントは、「修正案は出さなくて結構です」と先に伝えることです。これを言わないと、AIは指摘のあとに親切な修正コードを並べてくれて、そちらに目が行きます。いま欲しいのは、自分が見落としている順番の組み合わせです。
返ってきた筋書きは、そのまま信じずに1つずつ「これは実際に起こせるか」を確かめてください。AIは、実際には1つのプロセスからしか呼ばれない処理に、並行の心配を持ち出すこともあります。全部に対応する必要はありません。 起こせるものだけ拾えば十分です。指摘の選り分け方はAIレビューの指摘を取捨選択する|直す・直さないの判断軸が使えます。
直し方まで進むなら、選択肢はそう多くありません。同時に触られる範囲を狭くする(そもそも共有しない)、DB側で守る(一意制約、行ロック、更新時に版を確認する楽観ロック)、順番を1本にする(キューに並べて1つずつ処理する)。いちばん安いのは、たいてい2つ目のDB側で守る形です。アプリ側でどれだけ気をつけても、別の経路から更新されれば終わりですが、制約はどの経路にも効きます。SQLまわりの確認はAIに書かせたSQLの確認ポイント|N+1・インデックス漏れとあわせてどうぞ。
3. 「壊れること」を確かめる仕掛けを1つ入れる
直したあと、直ったことをどう確かめるかが最後の壁です。ここも1つだけで構いません。
- 言語の競合検出を使う:たとえばGoならテスト実行に競合検出のオプションを付けられますし、C/C++やRustの周辺にも同種の検出ツールがあります。Rustのように、そもそもコンパイル時に危ない共有を弾く言語もあります。自分の言語に何が用意されているかをAIに聞くのは、いい質問です。
- 同時に叩くテストを1本だけ書く:同じ処理を数十回まとめて実行し、最後に③の条件が崩れていないかだけを確かめるテストです。「在庫が0未満になっていないこと」「登録件数がちょうど1件であること」。難しく作らなくて大丈夫です。
- DBの制約で受け止める:二重登録が怖いなら、一意制約を1つ足す。アプリが間違えても、データは壊れません。テストを書くより先に、これで止まることも多いです。
なお、こうしたテストは通ったからといって「ない」ことの証明にはなりません。あくまで、よくある型を1つ潰したという話です。ここは正直に見ておいたほうが、あとで楽になります。AIレビューを通しても残るものについてはAIにレビューさせてもバグが残る|人が見るべき観点にも書きました。
3つとも今日やる必要はありません。ステップ1の3行を書くだけでも、AIから返ってくるコードは変わります。
非同期・並行処理のチェックリスト
全部に○が要るわけではありません。気になった行だけで十分です。
① AIに頼む前に
- 同時に触られるデータ・行・ファイル・外部サービスを書き出したか
- どこから・どれくらいの頻度で呼ばれるかをAIに伝えたか
- 「崩したくない条件」を1行で書けているか
- その処理が本当に並列である必要があるか、一度考えたか
② 返ってきたコードで
- 「確認してから書き込む」のあいだに、割り込まれる余地はないか
- 待ち合わせをまたいだ前後で、前提が変わっていないか
- 使い回してはいけないものを、複数の処理で共有していないか
- 並列数に上限があるか(全件を一斉に投げていないか)
- タイムアウトが設定されているか(待ち続ける経路が残っていないか)
- リトライされたとき、同じ処理が2回実行されても平気か
- 複数のロックを取る順番が、どの経路でもそろっているか
③ 出す前に
- 崩したくない条件を、DB側の制約でも守れないか検討したか
- 同時実行を再現するテストか、競合検出の仕組みを1つ入れたか
- 失敗したときに、どこまで進んだか分かるログが残るか
- 本番で異常が出たとき、いったん並列をやめて直列に戻せるか
この記事のまとめ
AIは非同期・並行処理のコードを、驚くほど速く、それらしく書きます。得意なのは書くことと、壊れ方を説明すること。苦手なのは、あなたのシステムで何が同時に起こるかを知ることです。それは、渡していない情報だからです。
やることは3つだけです。①「同時に触られるもの・同時に起こる操作・崩したくない条件」を3行書く/②「修正案は出さなくていいので、壊れる筋書きを時系列で書いて」と頼む/③壊れることを確かめる仕掛けを1つ入れる。この3つがあると、並行処理は「祈りながらリリースする作業」から、「決めて確かめる作業」に近づきます。

並行処理を怖いと感じるのは、目に見えない順番を頭の中だけで追いかけているからだと思います。それを面倒がらずにやろうとしている時点で、かなり丁寧な仕事です。今日ひとつ「これ、同時に来たらどうなるだろう」と問えたなら、それだけで、いちばん忙しい日に鳴る電話をひとつ減らしています。
よければ、こちらも
- そもそも任せる範囲を切りたいときはAIコーディングは小さく切る|関数1個から始めるスコープ設計
- 返ってきたコードをまとめて確かめたいときはAI生成コードのレビュー・検証チェックリスト|使う前に見る所
- 動作確認の型から整えたいときはAI生成コードの動作確認|そのまま使わない最低限の型
- 落ちたあとの調査を速くしたいときはスタックトレースをAIに読ませる|バグ調査を速くする渡し方