データ競合(データきょうごう、)は、単一のデータに対する同時読み書きが非一貫性を引き起こす現象、あるいはその状態である1.2 データの競合とは (Sun Studio 12: スレッドアナライザユーザーズガイド)。
概要 データ競合とは、「同時」に起きた読み書きにより、同じ時点において操作されたはずのデータが異なる状態を指し示す現象またはその状態である。
マルチスレッドのプログラムにおいて、データ競合は次の条件がすべて満たされた場合に発生する。
1つのプロセス内の2つ以上のスレッドがメモリ上の同じ場所に同時にアクセスする。 そのアクセスの少なくとも1つが書き込みアクセスである。 どのスレッドも、排他的ロックを使用して、そのメモリへのアクセスを制御していない。
これら3つの条件が成立すると、アクセス順序が非決定的となり、アクセス順序により、実行のたびに演算結果が異なることがある。
例えば2つのスレッド(スレッドA・スレッドB)が存在し、初期値が1の共有変数xにアクセスするとする。スレッドAはxの読み取り (read) を、スレッドBはxに値2の書き込み (write) を試みる。スレッドAとスレッドBが「同時」にxへの操作を行なった場合、結果はどうなりうるであろうか。1つの可能性はスレッドAが1を読み取りスレッドBが2を書き込む形であり、他の候補としてはスレッドBが2を書き込みスレッドAが2を読み取ることもありうる。あるいは本来同時に行ない得ない操作を同時実行してしまったことで未定義の振る舞いをするかもしれない。この一貫性を失なわせる現象、あるいは一貫性の失なわれた状態が「データ競合」である。
データ競合は「同時」に行なわれた操作によって発生するため、シングルスレッドプログラムでは発生せず、マルチスレッドプログラムにおいてのみ発生しうる。並列計算(parallel computing)/並行計算(concurrent computing)の分類は無関係である。シングルコアのCPU上では、(SMT環境を除き)マルチスレッドによる並列計算は処理時間短縮の効果がなく意味をなさないが、並行計算は応答性の改善などの効果があるため、シングルコア環境での並行計算にもマルチスレッドが使用されることは多い。シングルコアCPU上では、複数のスレッドが物理的に同時動作することはなく、オペレーティングシステムが短時間で実行コンテキストを切り替えることであたかも同時に動作しているかのようにみせかけているだけだが、データ競合は起こりうる。
データ競合を回避するには、ロックによる排他制御や、アトミック操作を利用するAndroid 用 SMP Primer | Android デベロッパー | Android Developers。
シングルスレッドプログラムであっても、割り込みにより、片方の処理を実行途中で一時停止し、もう一つの処理に切り替えて実行している場合、広義のデータ競合が起こりうる。これは「割り込み干渉」と呼ばれる。車載制御ソフトウェアなど、組み込みシステムでは割り込み処理が多用されるため、割り込み干渉の考慮と未然防止対策は必須である。
共有メモリやファイルなど、複数のプロセス間で共有されるリソースへの同時アクセスに関しても、排他制御しなければデータ競合が発生しうる。
害のない良性のデータ競合2.6 良性のデータ競合 (Sun Studio 12: スレッドアナライザユーザーズガイド)もあるが、多くのデータ競合はプログラムのバグとなる。
高水準言語のコード上において、異なる変数へのアクセスであるかのように見えても、メモリ上では同じバイトやワードへの読み書きとなる場合もあり、複数のスレッドでそれぞれの変数を同時に操作するとデータ競合となることがある(特にビットフィールド)CON32-C. 複数スレッドによる隣接データへのアクセスが必要な場合データ競合を防止する。
定義 非一貫性という危険な振る舞いを起こしうるデータ競合は、それぞれのプログラミング言語仕様において厳密に定義されている。
C/C++ C言語およびC++においては、C99 (ISO/IEC 9899:1999) およびC++03 (ISO/IEC 14882:2003) まではスレッドの概念が標準化されていなかったため、言語標準規格の仕様書にはデータ競合に関する文面が存在しない。C11 (ISO/IEC 9899:2011) およびC++11 (ISO/IEC 14882:2011) にてスレッドが標準化されたことで、以下のようなデータ競合に関する文面が追加された。
Java Javaにおいては、プログラムがhappens-before関係によって順序付けられていない2つの競合するアクセス(同じ共有変数に対する2つの読み書きアクセスのうち、少なくとも1つが書き込みであるようなアクセス)を含んでいるとき、データ競合を含んでいると言われる。
JavaScript JavaScript (ECMAScript) において、read/writeイベントEとDが以下の条件を満たしている場合「EとDはデータ競合にある」と定義されるECMAScript 2019 Language Specification. 27.9 Data Races。
EあるいはDに順序が定義されない(not SeqCst) EとDが重複したrangeを有する(overlapping range)
データ競合と競合状態 データ競合と競合状態は、似て非なる概念である。データ競合のないプログラムであっても、競合状態がないとは限らない。たとえば共有変数自体に対する読み書きが排他制御もしくはアトミック操作によって保護されている場合、少なくとも書き込み途中の中途半端なデータが他のスレッドから読み取られることはない(データ競合は発生しない)が、変更される前の値を他のスレッドが読み取るか、それとも変更された後の値を読み取るかによって、その後のプログラムの動作が変わる可能性がある。
Rustではunsafeコードを使用しない限り、データ競合は発生しないことが保証されるThe Rustonomicon (日本語訳) | 8. 並行性 - 8.1 競合。しかし、競合状態の防止に関しては、より粒度の大きい排他制御を明示的に記述するなどの対処をする必要があり、それはプログラマの責任となる。
脚注
注釈
出典
関連項目 メモリ一貫性 競合状態 特異なバグ ABA問題