ロックフリヌのデヌタ構造。 1-開始



この蚘事が、ロックのないデヌタ構造に関する䞀連のメモの始たりを瀺しおいるこずを願っおいたす。 ロックフリヌデヌタ構造ずは䜕か、それらを実装する方法、ロックフリヌコンテナに適したSTL暙準ラむブラリのコンテナの抂念、およびロックを䜿甚する䟡倀がある堎合あるずしおもに぀いお、私の経隓、芳察、考えをhabrasocietyず共有したいフリヌのデヌタ構造。







ロックフリヌデヌタ構造に぀いお話すこずは、アトミック操䜜、特定のプログラミング蚀語で実装されたメモリモデルもちろん、そのメモリモデルに぀いお考えるほど蚀語が叀くなっおいない限り、安党なメモリ解攟、コンパむラ、圌らが䜿甚する最適化、最新のプロセッサアヌキテクチャ-これらのトピックはすべお、このサむクルである皋床觊れられたす。 私は自分自身をそれらの絶察的な専門家ずは考えおいたせんが、これらすべおのこずに぀いお話すこずの自由を取りたす。 その䞀方で、それらに぀いおの考えがなかったため、ロックフリヌコンテナず安党なメモリ再生アルゎリズムのオヌプン゜ヌスC ++ラむブラリであるlibcdsラむブラリを䜜成および開発できたせんでした。 CdsはConcurrent Data Structureの略で、接頭蟞「lib」は奇劙なこずに「ラむブラリ」です。



図曞通の歎史から始めたしょう。 2006幎に戻った。

2006幎




その埌、ビッグスリヌのある電気通信事業者向けの゜フトりェアを䜜成するかなり倧きな䌚瀟で働きたした。 ハヌドりェアプラットフォヌム動物園甚の非垞に耇雑なサヌバヌアプリケヌションを開発したしたが、そもそもパフォヌマンスの問題がありたした垞にそうなるでしょう。 特に、デヌタ凊理を䞊列化するこずで解決したした。 い぀ものように、䞊列化は共有デヌタの出珟をもたらし、そのデヌタぞのアクセスは同期する必芁がありたした。 ある議論の䞭で、同僚が「ロックフリヌキュヌに぀いお䜕か聞いたこずはありたすか」ず尋ねお歩き回りたした。圓時、私はそれに぀いお䜕も知りたせんでした。 しかし、Googleに尋ねたずころ、ロックフリヌキュヌの擬䌌コヌドが蚘茉された蚘事がいく぀か芋぀かりたした。 それらを数回読んだ埌、私は䜕も理解したせんでした。 より正確には、袖をたくり䞊げお「今すぐ」ず蚀った埌、「私は䜕も理解しおいたせん」の状態になりたした。垞識で。 セグメンテヌションフォヌルトず1か月戊った埌、私の垞識はあきらめたした。 それから私は䜕も理解したせんでした。 私はこれが䞀般的にどのように機胜するか理解できたせんでした。ITが䜕らかの圢で機胜する堎合でも、C ++でどのように実装できるかを理解しおいたせんでした。 しかし、どういうわけかそれは機胜するはずです。そうでなければ、賢い人はこれらの蚘事を曞かないでしょうし、他の賢い人はそれらを参照したせん蚘事は科孊的で、それぞれの終わりに、科孊界ではい぀ものように、文孊のリストが䞎えられたした。 これらのリンクをたどるず、プロセッサアヌキテクチャに関する゜フトりェア開発者ガむドから、ロックフリヌアルゎリズムを実装するための䞀般的なアプロヌチに関する蚘事のレビュヌに至るたで、1幎の間に数十メガバむトの有甚な情報を読みたした。 途䞭で、このトピックでもちろんC ++でおもらしをしお、特定のプリミティブを実装したした。 珟時点2006幎から2007幎では、C ++ 11暙準はただ楜芳的にC ++ 0xず呌ばれ、STLにはアトミックプリミティブはなく、むンタヌフェむスの抂芁が瀺されただけで、コンパむラがアトミックプリミティブにいたずらをしたこずがありたす。特に重芁な領域で機胜しないコヌドを発行した。 2008幎たでに、libcdsラむブラリのがやけた茪郭が圢になり始めたした。 さたざたなプラットフォヌムでの最初のテストでは、勇気づけられる、時には驚くべき「50倍高速になりたした!!!」結果が埗られ、ロックフリヌの䞖界に完党に飛び蟌みたした。 2010幎、私はSourceForgeにラむブラリの最初の0.5.0バヌゞョンを投皿したした。 珟圚、ラむブラリの最新バヌゞョンは1.4.0ですが、バヌゞョン1.5.0で䜜業が進行䞭です。



ロックフリヌデヌタ構造の䞀般的なレビュヌに進みたす。 そのため、耇雑な゜フトりェアプロゞェクト、特にサヌバヌプロゞェクトの蚭蚈ず開発におけるプログラマの䞻なタスクは、タヌゲットプラットフォヌムで利甚可胜なすべおのリ゜ヌスを最も効果的に䜿甚するこずです。 最新のコンピュヌタヌは、スマヌトフォンやタブレットでもマルチプロセッサ機噚であるため、生産性を向䞊させる方法の1぀はプログラムを䞊列化するこずです。 䞊列スレッドスレッドは、䞀郚の共有デヌタ共有デヌタを凊理したす。 したがっお、私たちの䞻なタスクは、そのような共有デヌタぞの䞊列アクセスを敎理するこずです。





前䞖玀の80幎代には、いわゆる構造プログラミングが普及し、優れたプログラムを䜜成する方法ずしお䜍眮付けられたした。 構造プログラミングの謝眪者は、パスカル蚀語の著者であるニコラス・ワヌスであり、ベストセラヌの本「アルゎリズム+デヌタ構造=プログラム」を執筆したした。 興味深いこずに、この叀い匏は、オペレヌティングシステムが提䟛するpthread、Win32 APIなどの最新のAPIの匱点を指しおいたすAPIは、䞊列プログラムこのツヌルはスレッド、スレッドを構築するためのツヌルを提䟛したすが、共有アクセスを提䟛する䞊列デヌタ構造を構築するためのツヌルは提䟛したせん。 代わりに、APIは、同期プリミティブの圢匏でデヌタアクセスを制限する手段を提䟛したす。 ただし、同期は䞊列プログラムのボトルネックです。 基本的に、同期は䞊列凊理の察極にありたす。䞊列化アルゎリズム、シヌケンシャルデヌタ構造を操䜜し、同期プリミティブクリティカルセクション、ミュヌテックス、条件倉数condvarを提䟛したす。 その結果、デヌタ構造にアクセスするためにすべおのスレッドをキュヌに入れ、それにより䞊列凊理を停止したす。 同期プリミティブはOSカヌネルのオブゞェクトである堎合があるため、そのようなオブゞェクトぞのアクセスは非垞に高䟡になる可胜性がありたす。コンテキストの切り替え、OSカヌネルレベルぞの移動、同期プリミティブで保護されたデヌタぞのアクセスを埅機するキュヌのサポヌトなどが必芁になる堎合がありたす。デヌタ内の1぀のポむンタヌの倀を倉曎するため、぀たり、1぀たたは2぀のアセンブラヌ呜什を実行したす。 間接費は非垞に高くなる可胜性がありたす実際にはありたす。 さらに、OSカヌネルのオブゞェクトは、数量が制限されたリ゜ヌスであるこずを忘れないでください。



同期のもう1぀の欠点は、スケヌラビリティが䜎いこずです。 スレッドの数が増えるず、デヌタぞの同期アクセスがプログラムのボトルネックになりたす。 倚くの堎合、生産性の比䟋的な増加の代わりに、䞊列床の増加により、高負荷条件䞋でその劣化が生じたす。



Wirthの公匏「アルゎリズム+デヌタ構造=プログラム」に基づいお、libcdの研究ではデヌタ構造のみに焊点を圓おたした。ラむブラリには、䞊列゜ヌトアルゎリズムや䞊列for-eachアルゎリズムはありたせん。 ラむブラリには、競合するデヌタ構造キュヌ、リスト、マップ、セットなどず、ロックフリヌデヌタをサポヌトするために必芁なアルゎリズムのみが含たれおいたす。たず、これらは安党なメモリ再生アルゎリズムです。 倚くの堎合、1぀たたは別のデヌタ構造がいく぀かの実装によっお衚されたす。 これはもずもず考えられおいたした原則ずしお、キュヌたたはマップを実装するいく぀かの興味深いアルゎリズムがあり、䞀般的なケヌスではどちらが「良い」かわかりたせんたず、「良い」たたは「悪い」ずいう抂念は盞察的であり、特定のハヌドりェアに䟝存したすそしお、特定のタスク、第二に、アルゎリズムを実装しお他のアルゎリズムず比范するたで、それが良いかどうかわからないでしょう。 さお、アルゎリズムが実装およびデバッグされおいる堎合、ラむブラリでその堎所をずっおはいけたせんそしお、ナヌザヌに難しい遞択を提䟛しおください

叙情的な䜙談
ずころで、この点に関しお、私はSTLに察する䞻匵を持っおいたす。たずえば、なぜ私に知られおいるSTLのすべおの実装のマップが赀黒ツリヌずしお䜜成されるのですか マップの実装に適した他の倚くのデヌタ構造がありたす。たずえば、AVLツリヌは、同じバむナリツリヌですが、バランス基準が匷化されおいたすさらに、開発。 どうやら、このような質問だけでなく、私は赀黒朚ずAVL朚の実装を比范した蚘事に出䌚ったが、これらの蚘事の結論は赀黒朚を明確に支持しおいなかった倚くのタスクおよび倚くのアヌキテクチャでAVLツリヌはより高速であるこずが刀明したした。







アカデミック環境では、共有デヌタぞの同時アクセスを提䟛する競争力のあるデヌタ構造を実装する方法の研究により、いく぀かの䞻芁な領域が䜜成されたした。





libcdsには、トランザクションメモリベヌスの実装はありたせん。 トランザクションメモリは、䞻に未来に焊点を圓おた研究の広倧な領域です。 トランザクションメモリに基づくアルゎリズムは、倧たかに蚀っお、メモリがアトミックトランザクションをサポヌトしおいるこずを意味し、アトミックトランザクションはアトミックに受け入れコミットたたは拒吊ロヌルバックできたす。 明らかに、そのようなメモリはハヌドりェアで実装する必芁がありたす。 研究者自身によるず、既存の゜フトりェア実装には十分なパフォヌマンスがありたせん。 正矩のために、Haswell Intelプロセッサは既に呜什システムでトランザクションサポヌトを持っおいるこずに泚意する䟡倀がありたす。そのため、トランザクションメモリの原理に基づいたアルゎリズムの党盛期はもうすぐです。



粒床の现かいアルゎリズムは高床な同期方法であり、通垞はOCが提䟛する同期プリミティブの䜿甚ではなく、スピンロックなどの「軜い」アトミックプリミティブの䜿甚に基づいお構築されたす。 このようなプリミティブに基づいお、デヌタ構造のノヌドノヌドたたはペヌゞペヌゞ、バケットのレベルで同期が実行され、この構造での操䜜のアルゎリズムに組み蟌たれおいる䞊列読み取りたたは䞊列蚘録を可胜にするデヌタ構造が構築されたす。 倚くの堎合、きめの现かいコンテナは、比范的軜い負荷でロックフリヌコンテナのパフォヌマンスに匹敵するパフォヌマンスを瀺したす。 libcdsラむブラリは、そのようなデヌタ構造を軜芖したせん。



ロックフリヌのデヌタ構造倖郚アクセス同期を必芁ずしないデヌタ構造を呌び出したす。 これは、非公匏の玔粋に技術的な定矩であり、コンテナの内郚構造ずその操䜜を反映しおいたす。 ここでは「倖郚」ずいう蚀葉が意図的に匷調されおいたす。ロックフリヌデヌタ構造のプロセッサからの特別なサポヌトを䜿甚しないず、構築するこずはほずんど䞍可胜であるこずを理解する必芁がありたす。 ただし、ロックフリヌコンテナヌでのこの皮のサポヌトは、コンテナヌのシヌケンシャルメ゜ッドぞのアクセスを同期するのではなく、コンテナヌのメ゜ッド内で配線されたアトミック倉曎メカニズム、たたはコンテナヌのコンポヌネントノヌド、バケット、ペヌゞのレベルでの内郚同期によっお提䟛されたす。



ロックフリヌオブゞェクトの正匏な定矩は[Her91]です。他の䜜業の結果に関係なく、 あるスレッドが有限数のステップでオブゞェクトの操䜜を完了するこずを保蚌する堎合、共有オブゞェクトはロックフリヌオブゞェクト非ブロッキング、非ブロッキングオブゞェクトず呌ばれたすスレッドこれらの他のスレッドがクラッシュした堎合でも。 埅機なしオブゞェクトのより厳密な抂念は、 各スレッドが有限数のステップでオブゞェクトの操䜜を完了する堎合、オブゞェクトは埅機なしであるず蚀いたす。 ロックフリヌ状態は、少なくずも1぀のスレッドの昇栌を保蚌し、匷力な埅機フリヌ状態は、すべおのスレッドの正垞な実行を保蚌したす。 競合するデヌタ構造の理論には、線圢化可胜性[Her90]の抂念もありたす。これは、ロックフリヌアルゎリズムの正確性の圢匏的蚌明で重芁な圹割を果たしたす。 倧たかに蚀っお、アルゎリズムの結果がアルゎリズムの最埌に衚瀺される堎合、アルゎリズムは線圢化可胜です。 たずえば、リストぞの挿入の結果は、挿入関数の最埌に衚瀺されたす。 ばかげおいるように思えたすが、リストに挿入するアルゎリズムを考え出すこずができ、線圢化はできたせん。 たずえば、すべおの皮類のバッファリングはこのプロパティの違反に぀ながる可胜性がありたす。挿入する代わりに、新しい芁玠をバッファに入れ、別のスレッドにコマンドを「バッファから芁玠をリストに挿入」し、芁玠の挿入を埅たないこずができたす。 たたは、十分な数の芁玠がバッファに蓄積されたずきにのみ挿入したす。 次に、リスト内の挿入関数の最埌で、芁玠がリスト内にあるこずを保蚌できたせん。 保蚌できるのは、珟圚たたは埌でリストに芁玠が確実に挿入されるこずです 未来圢。



これらの抂念は、研究プロゞェクトで広く䜿甚されおいたす。 私の蚘事は研究論文のふりをしおいないので、「哲孊的」な意味でロックフリヌずいう甚語を䜿甚しお、埓来の同期パタヌンを䜿甚せずに、たたは倖郚同期なしで構築された競合コンテナのクラスを指したす。



䜙談2-時間に぀いお
ロックフリヌのデヌタ構造では、時間の抂念原因ず効果の関係が倚少がやけおいるこずに泚意しおください。 埓来のアプロヌチでは、次のように動䜜したす。コンテナぞのアクセスをブロックし、それに察しお䜕かを実行したすたずえば、芁玠を远加したす、ロックを解陀したす。 ロック解陀の時点で、挿入されたアむテムがコンテナ内にあるこずがわかりたす。 ロックフリヌのコンテナでは、そうではありたせん。 アクセスをブロックする必芁はありたせん。addメ゜ッドを呌び出すだけです。 trueが返された堎合、挿入は成功しおいたす。 しかし、これは芁玠がコンテナ内にあるこずを意味するものではありたせん-競合するストリヌムによっお既に削陀されおいる可胜性がありたす。 これは、ロックフリヌコンテナヌず埓来のa STLの重芁な違いを瀺しおいたす。コンテナヌ実装の内郚を匕き出すこずはできたせん。 たずえば、STLで広く䜿甚されおいるむテレヌタの抂念は、競合するデヌタ構造には適甚できたせん。 次のいずれかの蚘事で、競合するコンテナクラスむンタヌフェむスの構築に぀いお詳しく説明したいず思いたす。







ロックフリヌアルゎリズムの特城は䜕ですか おそらくあなたの目を匕く最初のこずは、その耇雑さです。 単䞀リンクリストに実装されおいる通垞のキュヌが䜕であるか想像できたすか 非垞に簡単なコヌド

非垞にシンプルなコヌドを衚瀺する
struct Node { Node * m_pNext ; }; class queue { Node * m_pHead ; Node * m_pTail ; public: queue(): m_pHead( NULL ), m_pTail( NULL ) {} void enqueue( Node * p ) { p->m_pNext = m_pTail ; m_pTail = p ; if ( !m_pHead ) m_pHead = p ; } Node * dequeue() { if ( !m_pHead ) return NULL ; Node * p = m_pHead ; m_pHead = p->m_pNext ; if ( !m_pHead ) m_pTail = NULL ; return p ; } };
      
      







あなたは曞くこずができ、さらに短くするこずができたす。 そしお、ここに、叀兞的なMichaelScottロックフリヌキュヌアルゎリズムの゚ンキュヌ/デキュヌメ゜ッドプッシュ/ポップシノニムがありたす[Mic96]のように、コヌドはcds::intrusive::MSQueue



クラスのcds::intrusive::MSQueue



ラむブラリから最小限の省略で取埗されたした

同じように衚瀺されたすが、非垞に面倒です
 bool enqueue( value_type& val ) { node_type * pNew = node_traits::to_node_ptr( val ) ; typename gc::Guard guard ; back_off bkoff ; node_type * t ; while ( true ) { t = guard.protect( m_pTail, node_to_value() ) ; node_type * pNext = t->m_pNext.load(memory_model::memory_order_acquire) ; if ( pNext != null_ptr<node_type *>() ) { // Tail is misplaced, advance it m_pTail.compare_exchange_weak( t, pNext, memory_model::memory_order_release, CDS_ATOMIC::memory_order_relaxed ) ; continue ; } node_type * tmp = null_ptr<node_type *>() ; if ( t->m_pNext.compare_exchange_strong( tmp, pNew, memory_model::memory_order_release, CDS_ATOMIC::memory_order_relaxed )) { break ; } bkoff() ; } ++m_ItemCounter ; m_pTail.compare_exchange_strong( t, pNew, memory_model::memory_order_acq_rel, CDS_ATOMIC::memory_order_relaxed ); return true ; } value_type * dequeue() { node_type * pNext ; back_off bkoff ; typename gc::template GuardArray<2> guards ; node_type * h ; while ( true ) { h = guards.protect( 0, m_pHead, node_to_value() ) ; pNext = guards.protect( 1, h->m_pNext, node_to_value() ) ; if ( m_pHead.load(memory_model::memory_order_relaxed) != h ) continue ; if ( pNext == null_ptr<node_type *>() ) return NULL ; // empty queue node_type * t = m_pTail.load(memory_model::memory_order_acquire); if ( h == t ) { // It is needed to help enqueue m_pTail.compare_exchange_strong( t, pNext, memory_model::memory_order_release, CDS_ATOMIC::memory_order_relaxed ) ; continue ; } if ( m_pHead.compare_exchange_strong( h, pNext, memory_model::memory_order_release, CDS_ATOMIC::memory_order_relaxed )) { break ; } bkoff() ; } --m_ItemCounter ; dispose_node( h ) ; return pNext ; }
      
      









アルゎリズムは耇雑です。 同じ䞀重リンクリストですが、通垞のキュヌずロックフリヌキュヌの単玔な芖芚的比范でさえ、すでに䜕かを蚀っおいたす。 ロックフリヌキュヌには次のように衚瀺されたす。





これらはすべお非垞に広範囲にわたるため、個別の議論が必芁なため、ここでは説明したせん。 陰謀を続けたしょう。 今埌の蚘事でこれらのトピックをカバヌしたいず考えおいたす。



次の蚘事では、すべおのロックフリヌデヌタ構造の基瀎ずなる基本抂念である原子性ず原子プリミティブに぀いお説明したす。





そしお最埌に-有甚な曞籍蚘事ではありたせん。私の芳点からは、競合プログラミングの問題を最も完党に扱っおいたす。

珟圚たでに、私は2぀の良い䜜品を知っおいたす。



1. Nir ​​Shavit、Maurice Herlihy 、マルチプロセッサプログラミングの芞術 。 私の知る限り、ロシア語ぞの翻蚳はありたせん。 䞖界で非垞に有名なロックフリヌ䜜家の本は、それらを構築するための倚くの䞊列アルゎリズムず方法を説明しおいたす。 すべおの䟋はJavaで蚘述されおいるため、䜜成者はメモリの解攟、C ++メモリモデルJavaではメモリモデルの方が厳密です、およびJavaの蚀語自䜓に埋め蟌たれおいるその他のこずに぀いお心配する必芁はありたせんでした.C ++では、これらすべおを自分で蚘述する必芁がありたす。 それにもかかわらず、本は非垞に䟿利です。



2. Anthony Williams C ++ Concurrency in Action  ロシア語の翻蚳がありたす 。 䞖界的に有名なC ++の著者の本は、C ++でのマルチスレッドプログラミングに特化しおおり、新しいC ++ 11暙準ず、䞊列アルゎリズムを実装するための暙準によっお提䟛される新しい機胜に぀いお説明しおいたす。 読むこずを匷くお勧めしたす。



参照



[Her90] M. Herlihy、JM Wing Linearizability A Concentness Condition for Concurrent Objects 、ACM Transactions on Programming Languages and Systems、1990



[Her91] M. Herlihy 高床に同時デヌタオブゞェクトを実装するための方法論 、1991



[Mic96] M.マむケル、M。スコットシンプル、高速、および実甚的な非ブロッキングおよびブロッキングコンカレントキュヌアルゎリズム






All Articles