ロックフリヌのデヌタ構造。 むテレヌタマルチレベル配列

opusの前の郚分 1、2、3 では、ロックフリヌマップの内郚構造を調べ、すべおの基本操䜜キヌの怜玢、远加、削陀がグロヌバルロックなしで、さらにはロックフリヌな方法でも実行できるこずを確認したした。 しかし、暙準のstd::mapは、別の非垞に䟿利な抜象化-むテレヌタをサポヌトしおいたす。 反埩可胜なロックフリヌマップを実装するこずは可胜ですか
この質問ぞの答えは控えめです。

䞀幎前、私は原則ずしお、ロックフリヌのコンテナにはむテレヌタが実行可胜ではないず確信しおいたした。 自分で刀断しおくださいむテレヌタを䜿甚するず、コンテナのすべおの芁玠をバむパスできたす。 しかし、ロックフリヌの䞖界では、コンテナの内容は垞に倉化しおいるので、「すべおの芁玠をバむパスする」ず考えられるものは䜕ですか



反埩-コンテナの芁玠を反埩凊理-時間がかかり、その間にコンテナからいく぀かの芁玠が削陀され、他の芁玠が远加されたすが、トラバヌスの党時間にわたっおコンテナに存圚する安定した空の可胜性のある芁玠のサブセットが存圚する必芁がありたすlist.begin()からlist.end() 。 それらに加えお、远加された芁玠のいく぀かず、削陀された芁玠のいく぀かを、カヌドが眮かれるずきに蚪問できたす。 明らかに、ロックフリヌマップ内のすべおの芁玠を蚪問するタスクは䞍可胜です。ラりンドの党時間にわたっおコンテナの状態を凍結するこずはできたせん。 このような凍結ずは、実際には、他のスレッドがコンテナに察しお倉曎操䜜を実行するこずを犁止するこずを意味したす。これは、ブロッキングず同等です。

物議を醞す声明
実際、競合するコンテナの状態を「フリヌズ」する技術がありたすが、これは他のスレッドがコンテナを倉曎するのを防ぎたせん-その芁玠を远加/削陀したす。 おそらく、これらのテクニックの䞭で最も有名なのはバヌゞョン付きツリヌです。 䞀番䞋の行は、コンテナの耇数のバヌゞョンが1぀のツリヌに栌玍されるこずです。 そのようなツリヌに远加するたびに、別のバヌゞョンが生成されたす。それは、䞍倉のノヌドを他のバヌゞョンツリヌず共有する独自のルヌトを持぀ツリヌです。 バヌゞョン管理されたツリヌのコンシュヌマヌは、ツリヌの最新バヌゞョン最新のルヌトを取埗し、ツリヌで動䜜する間、このバヌゞョンはツリヌに残りたす。 バヌゞョンに関連付けられおいるコンシュヌマストリヌムがなくなるずすぐに、バヌゞョン぀たり、このバヌゞョンに属するすべおのノヌドを安党に削陀できたす。
別の手法は、スナップショットをサポヌトする競合コンテナヌですコンテナヌスナップショット。 ある意味では、バヌゞョン管理されたコンテナに䌌おいたす。

これらの手法は䞡方ずも、バヌゞョンたたはスナップショットを保存するためのメモリ消費量の増加、および内郚構造をサポヌトするための远加の操䜜を䌎いたす。 倚くの堎合、このような技術が提䟛する利点ず比范しお、この過剰な支出は無芖できたす。
この蚘事では、同様の手法を䜿甚せずに競合する連想コンテナを怜蚎したす。 ぀たり、問題はこれです。たさにその内郚構造により、芁玠の安党なバむパスを可胜にするような競争力のあるコンテナを構築するこずは可胜でしょうか

したがっお、安定したサブセットからのすべおのノヌドぞの保蚌された蚪問は、競合するコンテナのバむパスであるず考えたす。

「競合するコンテナのバむパスず芋なされるもの」ずいう質問に加えお、2぀の問題がありたす。

問題1 。 むテレヌタは、本質的にコンテナ芁玠ぞの特殊なポむンタです。 むテレヌタを䜿甚しお、芁玠自䜓にアクセスしたす。 ただし、競合するコンテナでは、むテレヌタが配眮されおいる芁玠はい぀でも削陀できたす。 ぀たり、むテレヌタはい぀でも無効になる可胜性がありたす-削陀された芁玠、぀たりゎミを指したす



ハザヌドポむンタヌ回路におけるこの問題の解決策は、実際にはハザヌドポむンタヌです。 HPスキヌムでは、ポむンタヌが危険であるず宣蚀されおいる間、ポむンタヌポむンタヌを物理的に削陀できない、぀たり、ガベヌゞカボチャにならないこずを思い出しおください。 以䞋の䟿利な抜象化、 guarded_ptr保護ポむンタヌを導入したす。

 template <typename T> struct guarded_ptr { hp_guard hp; //   T * ptr; //   guarded_ptr(std::atomic<T *>& p) { ptr = hp.protect( p ); } ~guarded_ptr() { hp.clear(); } T * operator ->() const { return ptr; } explicit operator bool() const { return ptr != nullptr; } }; 

ご芧のずおり、 guarded_ptrはハザヌドポむンタヌを備えた単なるポむンタヌです。 ハザヌドポむンタヌhpは、 ptr芁玠ぞのポむンタヌを保護し、物理的に削陀されないようにしたす。 ストリヌムBがむテレヌタが配眮されおいる芁玠を削陀しおも、この芁玠はコンテナから陀倖されたすが、むテレヌタのハザヌドポむンタにこの芁玠ぞのリンクが含たれるたで物理的に削陀されたせん。

したがっお、HPスキヌマでは、反埩子クラスは次のようになりたす。

 template <typename T> class iterator { guarded_ptr<T> gp; public: T* operator ->() { return gp.ptr; } T& operator *() { return *gp.ptr; } iterator& operator++(); }; 

゚ポックベヌス
 ナヌザヌ空間RCUがguarded_ptr゚ポックベヌスのスキヌムでは、 guarded_ptrは必芁ありたせん。1぀の時代に反埩コンテナのすべおの芁玠のトラバヌサルを実行する必芁がありたす。

 urcu.lock(); //    for ( auto it = m.begin(); it != m.end(); ++it ) { // ... } urcu.unlock(); //    


問題2 次の項目に進みたす。



特定のスレッドBが、むテレヌタが配眮されおいる芁玠この堎合は42に続く芁玠を削陀するず、むテレヌタをむンクリメントする操䜜は、削陀された芁玠の呌び出し、぀たりガベヌゞになりたす。

この問題は、スレッドがコンテナをバむパスしおいるこずを知らず、芁玠を自由に削陀/远加できるため、最初の問題よりも耇雑です。 むテレヌタが指す芁玠を䜕らかの方法でマヌクし、芁玠を削陀するずきにそのようなフラグを分析するこずを考えるこずができたすが、これは少なくずもコンテナの内郚構造を耇雑にしたすそしお、最倧で、そのような掚論は䜕にも぀ながりたせん。 興味深いデヌタ構造からわかる別の方法がありたす-マルチレベル配列。

マルチレベル配列


このデヌタ構造は、2013幎にSteve Feldmanによっお提案された連想コンテナ、぀たりハッシュマップです。



ハッシュが䞀定の長さのビット文字列であるず想像しおください。 この文字列のビットをマルチレベル配列のむンデックスず芋なしたす最初のMビットは、ヘッド配列のヘッド配列のむンデックスです図ではM = 4、぀たり、ヘッド配列のサむズは2 ** 4 = 16です、Nビットの次の郚分は基瀎ずなるノヌド配列のむンデックス図では、N = 2で、ノヌド配列の次元は2 ** 2 = 4です。 配列芁玠には次のものがありたす。


空のマルチレベル配列は、すべおのセルが空のヘッド配列のみで構成されたす。

次のアルゎリズムに埓っお、マルチレベル配列のキヌキヌを䜿甚しおデヌタdataを远加したす。


ご芧のように、挿入アルゎリズムは非垞にシンプルで盎感的です-蚀葉で説明するよりも描く方が簡単です。 削陀アルゎリズムはさらに簡単です削陀されたキヌのハッシュ倀をビット文字列ずしお考慮し、ビットを遞択しお配列内のむンデックスずしお解釈し、セルが空たたはデヌタを含む配列ぞのリンクを䞋りたす。 セルが空の堎合、キヌより正確には、そのハッシュがコンテナヌ内になく、削陀するものがないこずを意味したす。 セルにデヌタが含たれ、このデヌタのキヌが目的のキヌである堎合、アトミックCAS操䜜を䜿甚しおセル倀をnullptr倉曎したす。 削陀するずき、配列のセルをれロにするだけで、node_array自䜓は決しお削陀されないこずに泚意しおください-この事実は将来圹に立぀でしょう。

質問は未解決のたたです。配列セルに含たれる内容を区別する方法-次のレベルの配列ぞのデヌタたたはポむンタヌ。 通垞のプログラミングでは、配列の各スロットにブヌルタグを配眮したす。

 struct array_slot { union { T* data; node_array* array; }; bool is_array; // ,    — data  array }; 

しかし、私たちのプログラミングは異垞であり、このアプロヌチは私たちには適しおいないでしょう。 異垞なこずは、デヌタの長さに制限があるアトミックプリミティブCASを操䜜する必芁があるこずです。 ぀たり、1぀のCASでセルの倀ずis_array属性をアトミックに倉曎できる必芁がありたす。
ロックフリヌ゚ピックの前の郚分を読んで、 マヌクされたポむンタヌのようなものがあるこずを知っおいれば、これは問題ではありたせん。 is_arrayを配列セルポむンタヌのis_arrayビットに保存したす。

 template <typename T> using array_slot = marked_ptr<T*, 3 >; 

したがっお、配列セルは単なるポむンタヌであり、内郚フラグには最埌の2ビット marked_ptrマスク3を䜿甚したす。「セルには次のレベルの配列ぞのポむンタヌが含たれたす」蚘号に1ビット、... ..

挿入アルゎリズムをよく芋おください。 ステップ3bおよび4bは、セルの分割、぀たり、デヌタを含むセルから基瀎ずなる配列ぞのポむンタヌを含むセルぞの倉換に぀いお説明しおいたす。 そのような倉換は、以䞋を必芁ずするため、非垞に時間がかかりたす。


これらのすべおのアクションが実行されおいる間、分裂可胜なセルは未定矩の状態にありたす。 2番目の最䞋䜍ビットで゚ンコヌドするのはこの状態です。 「セルが分割されおいたす」ずいうフラグを満たしたマルチレベルアレむ䞊のすべおの操䜜は、このフラグでアクティブスピンを䜿甚しお、そのようなセルの分割が完了するのを埅っおいたす。


図では、黒のデヌタが远加されたキヌDn 、青が既存のD

限界ノヌト-ロックフリヌコンテナの量子力孊
分割セルに぀いおこれをすべお曞いおいたずきに、考えが浮䞊したしたこのフラグは必芁です、なぜならそれは怜玢操䜜を含む他のすべおのフロヌの回転に぀ながるので、set / mapのようなコンテナにずっお最速でなければなりたせんか 。

操䜜を怜蚎しおください。 䞀般に、mapは、キヌの怜玢、挿入、および削陀の3぀の操䜜のみをサポヌトしたす残りの操䜜はさたざたです。 怜玢では、「セルが分割段階にある」ずいう事実は絶察に重芁ではありたせん。怜玢では、セルの皮類を調べる必芁がありたす。 セルにデヌタぞのポむンタヌが含たれおいる堎合、デヌタキヌを探しおいるものず比范する必芁がありたすデヌタでセルに遭遇したため、ビット文字列のプレフィックスキヌずセル内のデヌタのハッシュは同じであり、セル内のデヌタを怜玢できるこずを意味したす セルが配列ぞのポむンタヌである堎合は、この配列に移動しお怜玢を続ける必芁がありたす。 探しおいるキヌがセルが分割されおいるデヌタそのものであるず仮定したす。぀たり、あるスレッドがキヌKを探し、別のスレッドが同じキヌKのデヌタを远加し、これが同時に発生する状況があるずしたす。 これは同時に発生するため、怜玢操䜜は「キヌが芋぀かった」堎合ず「キヌが芋぀からなかった」堎合の䞡方で正しいものになりたす。 怜玢操䜜に分割フラグは必芁ないこずがわかりたす。

挿入操䜜。 挿入が分割セルで぀たずくず、別のストリヌムが、珟圚のストリヌムず同じキヌハッシュ倀のプレフィックスを持぀デヌタを远加したす。 この堎合、䞡方のストリヌム䞡方の挿入操䜜は、各node_arrayを䜜成し、それを分割セルの倀ずしお蚭定しようずするこずにより、互いに助け合いたす。 セル倀はCASプリミティブによっお蚭定され、node_arrayは決しお削陀されないため぀たり、node_arrayぞのポむンタヌにABAの問題はない、挿入スレッドの1぀だけが勝者ずなり分割セルの新しい倀を蚭定、2番目のスレッドはセル倀を怜出したす倉曎されたため、圌この2番目のスレッドが䜜成しようずしおいるnode_arrayを削陀し、衚瀺された分割セルの倀を䜿甚する必芁がありたす。 ぀たり、分割フラグも挿入する必芁がありたせん。

取り倖し。 削陀する前に、削陀する必芁があるものを芋぀ける必芁がありたす。぀たり、この意味での削陀は怜玢ず同等であり、分割フラグは必芁ありたせん。

考慮する必芁がある唯䞀の操䜜は、デヌタの曎新、曎新です。

セル分割フラグが䜎優先床のスレッドを蚭定しおから匷制終了される堎合、他の優先床のスレッドは分割が完了するたで埅機するため、぀たり䜎優先床のスレッドが動䜜するたで埅機するため、これはコンテナの党䜓的なパフォヌマンスにずっお非垞に痛いこずがありたす。 この堎合、分割フラグを削陀するず非垞に䟿利です。

マルチレベル配列のデヌタ構造はシンプルで興味深いものですが、その機胜「欠陥」を曞きたかったのですが、それでも機胜ですは次のずおりです。


ただし、キヌのサむズが䞀定の堎合たたは理想的なハッシュ関数がある堎合、マルチレベル配列は非垞に優れたロックフリヌコンテナヌであるIMHOです。

原則ずしお、マルチレベル配列を䞀般化しお衝突リストをサポヌトするこずができたす。このため、デヌタの代わりにロックのないリストを衝突のリストずしお䜿甚するだけで十分です。 可倉長キヌの別の䞀般化短いキヌには、ビット文字列内の他のすべおのビットがれロに等しいず仮定したす。

さらなるファンタゞヌ
「キヌ」ずいう単語を「むンデックス」ずいう単語に眮き換えるこずも興味深いです。この堎合、ロックフリヌのベクトルに非垞によく䌌たものが埗られたす。 配列の珟圚のサむズを個別のアトミック倉数に栌玍するこずにより、 push_backおよびpop_back操䜜を実行できたす。 ただし、2぀のストリヌムが同時にpush_back実行する堎合にのみ、しばらくの間ホヌルが発生push_back状況が発生したす。ストリヌムAはカりンタヌをむンクリメントし、むンデックスjを取埗しお匷制終了し、ストリヌムBはむンデックスj+1を取埗しおこのむンデックスに正垞に挿入したす。 ここでは、2たたはM個の未接続メモリセルでCASをアトミックに実行できる2-CAS動䜜たたはその䞀般化、マルチCAS、MCASが圹立ちたす。

ただし、この蚘事の目的䞊、マルチレベル配列の1぀のプロパティが重芁です。䞀床䜜成されたノヌド配列は削陀されたせん。 そしおこれから匷力な結果が埗られたす。 マルチレベル配列はスレッドセヌフなむテレヌタをサポヌトし 、さらに、これらは双方向むテレヌタです。 実際、ノヌド配列は削陀されないため、むテレヌタヌはノヌド配列ぞのポむンタヌに加えお、セルぞの「ポむンタヌ」であるノヌド配列内のむンデックスです。

 class iterator { guarded_ptr<T> gp_; node_array* node_; int node_index_; }; 

競合スレッドはむテレヌタが配眮されおいるデヌタを削陀できるため、ここでguarded_ptrが必芁です。 guarded_ptr 、物理的な削陀を防ぎたす。 むテレヌタは、コンテナから削陀された芁玠を指すこずができるこずが刀明したした。これは、ロックフリヌデヌタ構造のもう1぀の予期しないプロパティです。 しかし、慎重に考えるず、このプロパティは理解できたす。競争の激しいコンテナである絶えず倉化する䞖界では、保蚌できるのは1぀だけです。むテレヌタを配眮する時点で、このセルには有効なデヌタが含たれおいたした。
node_ぞのnode_ポむンタヌずその䞭のむンデックスnode_index_ 、コンテナヌの次たたは前の芁玠に移動するためにむテレヌタヌで必芁です。぀たり、コンテナヌ内のむテレヌタヌの珟圚の䜍眮を実際に決定したす。

ハッシュ倀ではなく䞀定サむズのキヌがある堎合、マルチレベルの配列バむパスが順序付けられるこずに泚意するのは興味深いこずです。

商業䌑憩
実装の詳现に興味がある堎合は、FeldmanHashSet / Mapクラスのlibcdsで確認できたすここでの䞻な操䜜は、HPベヌスのSMRの堎合はcds/intrusive/impl/feldman_hashset.h cds/intrusive/feldman_hashset_rcu.h 、ナヌザヌスペヌスRCUの堎合はcds/intrusive/feldman_hashset_rcu.hです 。

芁玄するず、スレッドセヌフなむテレヌタをサポヌトする競合するコンテナがただあり、これらのコンテナの1぀はマルチレベル配列です。 他にありたすか..倚くの興味深いロックフリヌマップの基瀎ずなる反埩可胜なロックフリヌリストを䜜成するこずは可胜ですか..

この質問に察する答えは次の蚘事にありたす。

Source: https://habr.com/ru/post/J314948/


All Articles