データ競合(データきょうごう、: data race)は、単一のデータに対する同時読み書きが非一貫性を引き起こす現象、あるいはその状態である[1]

概要 編集

データ競合とは、「同時」に起きた読み書きにより、同じ時点において操作されたはずのデータが異なる状態を指し示す現象またはその状態である。

マルチスレッドのプログラムにおいて、データ競合は次の条件がすべて満たされた場合に発生する。

  • 1つのプロセス内の2つ以上のスレッドがメモリ上の同じ場所に同時にアクセスする。
  • そのアクセスの少なくとも1つが書き込みアクセスである。
  • どのスレッドも、排他的ロックを使用して、そのメモリへのアクセスを制御していない。

これら3つの条件が成立すると、アクセス順序が非決定的となり、アクセス順序により、実行のたびに演算結果が異なることがある[1]

例えば2つのスレッド(スレッドA・スレッドB)が存在し、初期値が1の共有変数xにアクセスするとする。スレッドAはxの読み取り (read) を、スレッドBはxに値2の書き込み (write) を試みる。スレッドAとスレッドBが「同時」にxへの操作を行なった場合、結果はどうなりうるであろうか。1つの可能性はスレッドAが1を読み取りスレッドBが2を書き込む形であり、他の候補としてはスレッドBが2を書き込みスレッドAが2を読み取ることもありうる。あるいは本来同時に行ない得ない操作を同時実行してしまったことで未定義の振る舞いをするかもしれない[注釈 1]。この一貫性を失なわせる現象、あるいは一貫性の失なわれた状態が「データ競合」である。

データ競合は「同時」に行なわれた操作によって発生するため、シングルスレッドプログラムでは発生せず、マルチスレッドプログラムにおいてのみ発生しうる[注釈 2]並列計算(parallel computing)/並行計算(concurrent computing)の分類は無関係である。シングルコアのCPU上では、(SMT環境を除き)マルチスレッドによる並列計算は処理時間短縮の効果がなく意味をなさないが、並行計算は応答性の改善などの効果があるため、シングルコア環境での並行計算にもマルチスレッドが使用されることは多い。シングルコアCPU上では、複数のスレッドが物理的に同時動作することはなく、オペレーティングシステムが短時間で実行コンテキストを切り替えることであたかも同時に動作しているかのようにみせかけているだけだが、データ競合は起こりうる。

データ競合を回避するには、ロックによる排他制御や、アトミック操作を利用する[3]

シングルスレッドプログラムであっても、割り込みにより、片方の処理を実行途中で一時停止し、もう一つの処理に切り替えて実行している場合、広義のデータ競合が起こりうる。これは「割り込み干渉」と呼ばれる[4]。車載制御ソフトウェアなど、組み込みシステムでは割り込み処理が多用されるため、割り込み干渉の考慮と未然防止対策は必須である。

共有メモリやファイルなど、複数のプロセス間で共有されるリソースへの同時アクセスに関しても、排他制御しなければデータ競合が発生しうる。

害のない良性のデータ競合[5]もあるが、多くのデータ競合はプログラムのバグとなる。

高水準言語のコード上において、異なる変数へのアクセスであるかのように見えても、メモリ上では同じバイトやワードへの読み書きとなる場合もあり、複数のスレッドでそれぞれの変数を同時に操作するとデータ競合となることがある(特にビットフィールド[6]

定義 編集

非一貫性という危険な振る舞いを起こしうるデータ競合は、それぞれのプログラミング言語仕様において厳密に定義されている。

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) にてスレッドが標準化されたことで、以下のようなデータ競合に関する文面が追加された[7][8]

もしプログラムが異なるスレッドにおいて2つの競合する作用を含んでおり、そのうち少なくとも1つがアトミックではなく、かつどちらの作用も他方の作用の前に発生しない場合、そのプログラムの実行はデータ競合を含んでいる。このようなデータ競合はいずれも未定義動作を引き起こす。

Java 編集

Javaにおいては、プログラムがhappens-before関係によって順序付けられていない2つの競合するアクセス(同じ共有変数に対する2つの読み書きアクセスのうち、少なくとも1つが書き込みであるようなアクセス)を含んでいるとき、データ競合を含んでいると言われる[9]

JavaScript 編集

JavaScript (ECMAScript) において、read/writeイベントEとDが以下の条件を満たしている場合「EとDはデータ競合にある」と定義される[10]

  • EあるいはDに順序が定義されない(not SeqCst)
  • EとDが重複したrangeを有する(overlapping range)

データ競合と競合状態 編集

データ競合と競合状態は、似て非なる概念である。データ競合のないプログラムであっても、競合状態がないとは限らない。たとえば共有変数自体に対する読み書きが排他制御もしくはアトミック操作によって保護されている場合、少なくとも書き込み途中の中途半端なデータが他のスレッドから読み取られることはない(データ競合は発生しない)が、変更される前の値を他のスレッドが読み取るか、それとも変更された後の値を読み取るかによって、その後のプログラムの動作が変わる可能性がある。

Rustではunsafeコードを使用しない限り、データ競合は発生しないことが保証される[11]。しかし、競合状態の防止に関しては、より粒度の大きい排他制御を明示的に記述するなどの対処をする必要があり、それはプログラマの責任となる。

脚注 編集

注釈 編集

  1. ^ 例えばワードサイズが32ビット幅のマシンでは、64ビット幅のデータの読み書きを2回に分けて実行する必要があり、メモリ上で片方のワードだけが書き込まれた中途半端な状態(異常なデータ)を他のスレッドが観測してしまう可能性もある[2]
  2. ^ ここでは、割り込みにより、片方の処理を実行途中で一時停止し、もう一つの処理に切り替えて実行している状態は、「同時」には含まない。

出典 編集

  1. ^ a b 1.2 データの競合とは (Sun Studio 12: スレッドアナライザユーザーズガイド)
  2. ^ VNA05-J. 64ビット値の読み書きはアトミックに行う
  3. ^ Android 用 SMP Primer  |  Android デベロッパー  |  Android Developers
  4. ^ 稲森豊, 山田信幸「検出漏れの無い割込み干渉検出システムの開発」『SEC journal』第7巻第1号、独立行政法人 情報処理推進機構 ソフトウェア高信頼化センター、2011年、6-9頁、doi:10.11186/secjournal.7.6ISSN 13498622CRID 1390282680242595200 
  5. ^ 2.6 良性のデータ競合 (Sun Studio 12: スレッドアナライザユーザーズガイド)
  6. ^ CON32-C. 複数スレッドによる隣接データへのアクセスが必要な場合データ競合を防止する
  7. ^ INTERNATIONAL STANDARD Programming languages - C (N1570 Committee Draft) §5.1.2.4 Multi-threaded executions and data races” (PDF). ISO/IEC (2011年4月12日). 2023年1月4日閲覧。 “The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.”
  8. ^ Working Draft, Standard for Programming Language C++ (Document Number: N3337) §1.10 Multi-threaded executions and data races” (PDF). ISO/IEC (2012年1月16日). 2023年1月4日閲覧。 “The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.”
  9. ^ Chapter 17. Threads and Locks - §17.4.5. Happens-before Order”. Java SE 8 Specifications > Java Language Specification. Oracle. 2023年1月3日閲覧。 “When a program contains two conflicting accesses (§17.4.1) that are not ordered by a happens-before relationship, it is said to contain a data race.”
  10. ^ ECMAScript 2019 Language Specification. 27.9 Data Races
  11. ^ The Rustonomicon (日本語訳) | 8. 並行性 - 8.1 競合

関連項目 編集