TL;DR
プログラムの振る舞いが、制御できないイベントのタイミングや順序に左右される状態。起こりうる振る舞いのどれかが望ましくないとき、バグになる。
仕組み
並行に動く処理が、想定しない順序で終わると起きる。典型的な例は、2 つのスレッドが、0 で始まる共有の整数をそれぞれ 1 増やす処理である。読み出しと書き込みが交互に入り組むと、両方のスレッドが 0 を読んで 1 を書くので、期待した 2 ではなく 1 になる。
例
Effective TypeScript 項目47 では、window.user がセットされる前に greetUser を呼び出せてしまうことを、競合状態の例として挙げている。
注意点
共有の状態を扱う処理は、互いに排他的なクリティカルセクションに入れる。ロックやセマフォによる排他制御が、主な防ぎ方である。
データ競合を競合状態の一種とするかは、文献によって異なる。データ競合がなくても、タイミングによる非決定性は起こりうる。
結果が非決定的なので、再現が難しい。デバッグモードで動かしたり、ログを足したり、デバッガをつないだりすると消えてしまうことがあり、こうしたバグはハイゼンバグと呼ばれる。そのため、注意深い設計で競合状態を避けることが勧められている。
関連
- データ競合
- クリティカルセクション
- デッドロック
- スレッド
- TLA+: 競合状態などの設計の誤りを見つけるための形式仕様言語