ABA問題のソースを表示
←
ABA問題
ナビゲーションに移動
検索に移動
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループに属する利用者のみが実行できます:
登録利用者
。
このページのソースの閲覧やコピーができます。
{{脚注の不足|date=2023年1月10日 (火) 03:53 (UTC)}} '''ABA問題'''([[英語|英]]: '''ABA problem''')とは、[[スレッド (コンピュータ)|マルチスレッド]][[プログラミング]]において同期化の過程で発生する問題であり、ある記憶域を二回読み出し、二回の読み出しが同じ値であることを「変更がない」とみなすことにしたとき、二回の読み出しの間に別のスレッドが値を変更し、他の作業を行った後また元の値に戻すと、最初のスレッドが誤って「変更がなかった」とみなしてしまうというものである。 ABA 問題は、複数の[[スレッド (コンピュータ)|スレッド]]や(or [[プロセス]])が[[共有メモリ|共有されたメモリ]]にアクセスする場合に生じる。下記のイベントの流れは、ABA 問題を発生させる。 * プロセス <math>P_1</math> が共有メモリから値 A を読み出す * <math>P_1</math> は[[プリエンプション|プリエンプト]]され、プロセス <math>P_2</math> が実行される * <math>P_2</math> は、共有メモリの値 A を値 B に書き換え、さらにプリエンプションの前に A に書き戻す * <math>P_1</math> は実行を再開し、共有メモリの値が変更していないことを確認して実行を継続する <math>P_1</math> は実行を継続するが、共有メモリの変更が分からなかったことにより、誤った振る舞いをする可能性がある。 ABA問題に遭遇する一般的なケースとして、[[Lock-freeとWait-freeアルゴリズム|ロックフリー]]のデータ構造を実現する場合がある。ある要素がリストから削除され、解放された後、新しい要素が割り当てられて、リストに追加されると、新規に割り当てられた要素と、削除された要素がメモリ管理上の最適化によって同じものになることがよくある。新しい要素のポインタは古いものと同一になり、ここで ABA 問題を生じる。 == 例 == 下記の [[Lock-freeとWait-freeアルゴリズム|ロックフリー]][[スタック]]を考える: <syntaxhighlight lang="cpp"> /* ABA 問題を生じるロックフリースタック */ class Stack { volatile Obj* top_ptr; // // トップのオブジェクトをポップし、そのポインタを返す // Obj* Pop() { while(1) { Obj* ret_ptr = top_ptr; if (!ret_ptr) return NULL; Obj* next_ptr = ret_ptr->next; // トップのノードはまだ ret であり、スタックに変更があったと考えない // (これは、ABA 問題のために正しいとはいえない) // トップを次の要素に(アトミックに)変更 if (CompareAndSwap(top_ptr, ret_ptr, next_ptr)) { return ret_ptr; } // スタックは変更され、最初からやり直し } } // // obj_ptr で指すオブジェクトをスタックにプッシュする // void Push(Obj* obj_ptr) { while(1) { Obj* next_ptr = top_ptr; obj->next = next_ptr; // トップノードがまだ next であると、スタックに変更があったと考えない // (これは、ABA 問題のために正しいとはいえない) // トップを obj に(アトミックに)変更 if (CompareAndSwap(top_ptr, next_ptr, obj_ptr)) { return; } // スタックは変更され、最初からやり直し } } }; </syntaxhighlight> このコードは通常は並列的なアクセスの問題を生じないが、ABA 問題を生じる。下記のようなシーケンスを考える。 スタックが初期状態で トップ → A → B → C というデータを持っているとする。 まずスレッド 1 が top から開始し、 <syntaxhighlight lang="cpp"> ret = A; next = B; </syntaxhighlight> <code>CompareAndSwap</code>の直前に割り込まれると、 <syntaxhighlight lang="cpp"> { // スレッド 2 が pop を実行: ret = A; next = B; CompareAndSwap(A, A, B) // 成功, top = B return A; } // 新しいスタックは トップ -> B -> C { // スレッド 2 が pop を再び実行: ret = B; next = C; CompareAndSwap(B, B, C) // 成功, top = C return B; } // 新しいスタックは トップ -> C delete B; { // スレッド 2 が A をトップに載せる: A->next = C; CompareAndSwap(C, C, A) // 成功, top = A } </syntaxhighlight> 現在、スタックの内容は top → A → C である。 ここでスレッド 1 が再開すると、<code>CompareExchange(A, A, B)</code>が実行される。 この処理は、<code>top == ret</code> (いずれも A)であるから、必ず成功し、top を next (この場合B) に割り当てる。しかし、B は解放済みであるから、何らかの問題が生じる。 == 対策 == 一般的な対策としては、タグとなるビットを追加して変更を表現することである。たとえばポインタの[[コンペア・アンド・スワップ]]では、アドレスの下位のビットで、ポインタの示すデータが何回書き換わったかを表現する。これにより、アドレスが同じであってもタグビットが異なるため、次回のコンペア・アンド・スワップを失敗させることができる。タグビットはすぐに一周してしまうので、これで問題が完全に解決するわけではない。アーキテクチャによっては、ダブルワードの[[コンペア・アンド・スワップ]]を提供しており、もっと大きなタグを使うことができる。この場合には A が最初とわずかに異なっているので、 ABA' 問題と呼ばれることもある。 確実だが高価な方法として、データ要素そのものではない中間ノードを用いる方法がある。中間ノードはデータ要素のように変更・削除されないようにできるので、要素が挿入されたり削除された場合でも不変性を保証できる[Valois]。 他には、[[:en:hazard pointer|ハザードポインタ]]を用いる方法がある。これはリストにはない箇所を示すポインタである。ハザードポインタは変更中の状態を表現し、後でデータを同期する必要があることを示す[Doug Lea]。 アーキテクチャによってはもっと大きなアトミック操作を可能にするものがある。たとえば、二重リンクリストをアトミックに更新できるものもある。 また、アーキテクチャによっては[[Load-Link/Store-Conditional]]命令を提供しているものがあり、これはアドレスの示す箇所に他のストアが行われない場合に限りストアを実行するというものである。記憶域がある値を持っていることと、記憶域が変化したことという二つの概念をうまく分離することができる。[[DEC Alpha]] や [[PowerPC]] がこの命令をサポートしている。 == 参考文献 == # Damian Dechev, Peter Pirkelbauer, and Bjarne Stroustrup. [http://www.research.att.com/~bs/lock-free-vector.pdf Lock-free Dynamically Resizable Arrays] == 関連項目 == * [[並行性制御]] * [[メモリ一貫性]] * [[競合状態]] * [[データ競合]] * [[特異なバグ]] [[Category:並行性]]
このページで使用されているテンプレート:
テンプレート:脚注の不足
(
ソースを閲覧
)
ABA問題
に戻る。
ナビゲーション メニュー
個人用ツール
ログイン
名前空間
ページ
議論
日本語
表示
閲覧
ソースを閲覧
履歴表示
その他
検索
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
特別ページ
ツール
リンク元
関連ページの更新状況
ページ情報