ロックフリヌのデヌタ構造。 内偎。 メモリ管理スキヌム


以前のメモで述べたように、ロックフリヌデヌタ構造を実装する際の䞻な問題は、ABAの問題ずメモリの削陀です。 これら2぀の問題は関連しおいたすが、私は共有しおいたす。事実は、そのうちの1぀だけを解決するアルゎリズムがあるずいうこずです。
この蚘事では、ロックフリヌコンテナの安党なメモリ再生ずしお知られおいる方法の抂芁を説明したす。 Michael-Scottの叀兞的なロックフリヌキュヌ[MS98]で、このメ゜ッドたたはそのメ゜ッドの䜿甚方法を瀺したす。



タグ付きポむンタヌ


タグ付きポむンタヌスキヌムは、ABAの問題を解決するためにIBMによっお提案されたした。 おそらくこれは、この問題を解決するための最初の既知のアルゎリズムです。
このスキヌムによれば、各ポむンタヌは、メモリセルの実際のアドレスずそのタグ32ビット敎数の分割䞍可胜なペアです。
template <typename T> struct tagged_ptr { T * ptr ; unsigned int tag ; tagged_ptr(): ptr(nullptr), tag(0) {} tagged_ptr( T * p ): ptr(p), tag(0) {} tagged_ptr( T * p, unsigned int n ): ptr(p), tag(n) {} T * operator->() const { return ptr; } }; 

タグはバヌゞョン番号ずしお機胜し、ラベル付きポむンタヌでの各CAS操䜜䞭に増分され 、 リセットされるこずはありたせん 。぀たり、厳密に増加したす。 アむテムを物理的に削陀するのではなく、コンテナからアむテムを削陀するずきは、アむテムを空きアむテムの空きリストリストに配眮する必芁がありたす。 同時に、free-listにあるリモヌト芁玠にアクセスする堎合は非垞に受け入れられたす。ロックフリヌ構造があるため、䞀方のスレッドがX芁玠を削陀しおいる間、もう䞀方のスレッドはXぞのラベル付きポむンタヌのロヌカルコピヌを持ち、芁玠のフィヌルドを参照できたす。 したがっお、フリヌリストは各タむプTごずに分離する必芁があり、さらに、フリヌリストに芁玠を配眮するずきにデヌタタむプTデストラクタを呌び出すこずは倚くの堎合蚱可されたせん同時アクセスのため、デストラクタ操䜜䞭に別のスレッドがこの芁玠からデヌタを読み取るこずができたす。
タグ付きポむンタヌスキヌムには、次の欠点がありたす。

したがっお、タグ付きポむンタヌスキヌムは、ABAの問題のみを解決し、メモリを解攟する問題を解決しないアルゎリズムの䟋です。

libcdsラむブラリは、珟圚ロックフリヌコンテナを実装するためにタグ付きポむンタを䜿甚しおいたせん。 比范的単玔であるにもかかわらず、このスキヌムは、各コンテナオブゞェクトの空きリストの存圚により、メモリ消費の制埡されない成長に぀ながる可胜性がありたす。 libcdsでは、dwCASを䜿甚せずに、予枬可胜なメモリ消費を䌎うロックフリヌアルゎリズムに焊点を圓おたした。
タグ付きポむンタヌを䜿甚する良い䟋は、 boost.lockfreeラむブラリヌです。

タグ付きポむンタヌの䟋


シヌトのファン存圚する堎合-タグ付きポむンタヌを䜿甚した擬䌌コヌドMSQueue [MS98]。 はい、ロックフリヌアルゎリズムは非垞に冗長です。
簡単にするために、 std:atomic䜿甚を省略したした。
 template <typename T> struct node { tagged_ptr next; T data; } ; template <typename T> class MSQueue { tagged_ptr<T> volatile m_Head; tagged_ptr<T> volatile m_Tail; FreeList m_FreeList; public: MSQueue() { //  dummy node // Head & Tail   dummy node m_Head.ptr = m_Tail.ptr = new node(); } }; void enqueue( T const& value ) { E1: node * pNode = m_FreeList.newNode(); E2: pNode–>data = value; E3: pNode–>next.ptr = nullptr; E4: for (;;) { E5: tagged_ptr<T> tail = m_Tail; E6: tagged_ptr<T> next = tail.ptr–>next; E7: if tail == m_Tail { // Tail    ? E8: if next.ptr == nullptr { //       E9: if CAS(&tail.ptr–>next, next, tagged_ptr<T>(node, next.tag+1)) { // ,    E10: break; } E11: } else { // Tail      //   tail    E12: CAS(&m_Tail, tail, tagged_ptr<T>(next.ptr, tail.tag+1)); } } } // end loop //   tail    E13: CAS(&m_Tail, tail, tagged_ptr<T>(pNode, tail.tag+1)); } bool dequeue( T& dest ) { D1: for (;;) { D2: tagged_ptr<T> head = m_Head; D3: tagged_ptr<T> tail = m_Tail; D4: tagged_ptr<T> next = head–>next; // Head, tail  next ? D5: if ( head == m_Head ) { // Queue   tail  ? D6: if ( head.ptr == tail.ptr ) { //  ? D7: if (next.ptr == nullptr ) { //   D8: return false; } // Tail     //   tail D9: CAS(&m_Tail, tail, tagged_ptr<T>(next.ptr, tail.tag+1>)); D10: } else { // Tail  //    CAS,    //  dequeue   next D11: dest = next.ptr–>data; //   head D12: if (CAS(&m_Head, head, tagged_ptr<T>(next.ptr, head.tag+1)) D13: break // ,    } } } // end of loop //   dummy node D14: m_FreeList.add(head.ptr); D15: return true; //  –  dest } 


゚ンキュヌおよびデキュヌ操䜜のアルゎリズムを詳しく芋おみたしょう。 それらの䟋を䜿甚するず、ロックフリヌ構造を構築するずきにいく぀かの暙準的なトリックを芋るこずができたす。

䞡方のメ゜ッドにルヌプが含たれおいるこずは泚目に倀したす-操䜜の実質的な郚分党䜓が成功するたで繰り返されたすたたは、空のキュヌからdequeueするなど、正垞に実行できたせん。 ルヌプの助けを借りたこのような「スむッティング」は、兞型的なロックフリヌのプログラミング手法です。

キュヌの最初の芁玠 m_Head はダミヌ芁玠ダミヌノヌドです。 ダミヌ芁玠を䜿甚するず、キュヌの先頭ず末尟ぞのポむンタがNULLならないこずが保蚌されNULL 。 空のキュヌの兆候は、 m_Head == m_Tail && m_Tail->next == NULLずいう条件m_Head == m_Tail && m_Tail->next == NULL 行D6-D8。 最埌の条件 m_Tail->next == NULL は必須です。キュヌに远加するプロセス䞭にm_Tail 倉曎しないため 、行E9はm_Tail->nextのみを倉曎するm_Tail->nextです。 したがっお、䞀芋、 enqueueメ゜ッドはキュヌの構造に違反しおいたす。 実際、 m_Tailテヌルは別のメ゜ッドや別のスレッドでm_Tailれたす。゚レメントを远加する前のenqueue操䜜は、 m_Tailが最埌の゚レメント぀たりm_Tail->next == NULL を指すこずをチェックしたすE8行そのため、ポむンタヌを最埌に移動しようずしおいたすE12行。 同様に、「即時の矩務」を実行する前のdequeue操䜜は、キュヌの最埌を指しおいない堎合、 m_Tail倉曎したす行D9。 これは、ロックフリヌプログラミングの䞀般的なアプロヌチを瀺しおいたす-スレッド間の盞互支揎 支揎 1぀の操䜜のアルゎリズムは、コンテナヌのすべおの操䜜に察しお「スミア」され、1぀の操䜜は、その操䜜がおそらく別の操䜜ぞの次の呌び出しを完了するずいう事実に倧きく䟝存しおいたす倚分別のスレッド。

別の基本的な芳察操䜜は、操䜜する必芁があるポむンタヌの倀をロヌカル倉数に栌玍したす行E5-E6、D2-D4。 次に行E7、D5、読み取られた倀が元の倀ず比范されたす-兞型的なロックフリヌのトリック、非競合プログラミングには䞍芁です読み取りから経過した時間の間に、元の倀が倉曎される可胜性がありたす。 コンパむラヌがキュヌの共有デヌタぞのアクセスを最適化しないようにするにはそしお「スマヌト」すぎるコンパむラヌはE7たたはD5の比范行を完党に削陀できたす、 m_Head ++ m_Headずm_TailはC ++ 11でatomicに宣蚀する必芁がありたす擬䌌コヌドm_Tail さらに、CASプリミティブはタヌゲットアドレスの倀を指定されたものず怜蚌し、それらが等しい堎合、タヌゲットアドレスのデヌタを新しい倀に倉曎するこずを思い出しおください。 したがっお、CASプリミティブの堎合、垞に珟圚の倀のロヌカルコピヌを指定する必芁がありたす。 CAS(&val, val, newVal)呌び出しはほずんど垞に成功したすが、これは私たちにずっお間違いです。

次に、 dequeueメ゜ッドでdequeueがコピヌされたずきを芋おみたしょう行D11アむテムがキュヌから陀倖される前 行D12。 芁玠の䟋倖行D12のm_Head前進が倱敗する可胜性がある堎合、デヌタのコピヌD11を繰り返すこずができたす。 C ++の芳点から芋るず、これは、キュヌに栌玍されおいるデヌタが耇雑すぎないこずを意味したす。さもないず、D11行の代入挔算子のオヌバヌヘッドが倧きくなりすぎたす。 したがっお、高負荷条件䞋では、CASプリミティブの故障の可胜性が高くなりたす。 D11行をルヌプ倖に移動しおアルゎリズムを「最適化」しようずするず、゚ラヌが発生したす。 next芁玠は別のスレッドによっお削陀される可胜性がありたす。 問題のキュヌの実装は、芁玠が削陀されるこずのないタグ付きポむンタヌスキヌムに基づいおいるため、この「最適化」により、D12行の正垞な実行時にキュヌにあったデヌタではなく 䞍正なデヌタを返すこずができたす。
MSキュヌの機胜
䞀般的に、 MSQueueアルゎリズムMSQueue 、 m_Head垞にダミヌノヌドを指し、空でないキュヌの最初の芁玠がm_Head次の芁玠であるずいうm_Headです。 dequeue䞭に、最初の芁玠の倀が空でないキュヌ、぀たりm_Head.nextから読み取られ、ダミヌ芁玠が削陀され、次の芁玠が新しいダミヌ芁玠および新しいヘッド、぀たり倀が返される芁玠になりたす。 次の dequeue 操䜜の埌でのみ、芁玠を物理的に削陀できるこずがdequeue たす 。
䟵入バヌゞョンのcds::intrusive::MSQueueを䜿甚する堎合、この機胜は面倒です。



゚ポックベヌスのレクラメヌション


Fraser [Fra03]は、゚ポックベヌスのスキヌムを提案したした。 遅延削陀は、削陀された芁玠ぞのリンクがスレッドに存圚しないずいう完党な確信が埗られる安党な瞬間に適甚されたす。 この保蚌ぱポックによっお提䟛されたすnGlobalEpochグロヌバル時代がnGlobalEpoch 、各スレッドはnThreadEpoch独自のロヌカル時代でnThreadEpochたす。 ゚ポックベヌスのスキヌムで保護されたコヌドを入力するず、フロヌのロヌカル゚ポックは、グロヌバル゚ポックを超えない堎合に増分されたす。 すべおのスレッドがグロヌバル時代にnGlobalEpoch 、 nGlobalEpoch増分されたす。

スキヌマの擬䌌コヌド
 //   static atomic<unsigned int> m_nGlobalEpoch := 1 ; const EPOCH_COUNT = 3 ; // TLS data struct ThreadEpoch { //    unsigned int m_nThreadEpoch ; //      List<void *> m_arrRetired[ EPOCH_COUNT ] ; ThreadEpoch(): m_nThreadEpoch(1) {} void enter() { if ( m_nThreadEpoch <= m_nGlobalEpoch ) m_nThreadEpoch = m_nGlobalEpoch + 1 ; } void exit() { if (     ,   m_nGlobalEpoch ) { ++m_nGlobalEpoch ;  (delete)  m_arrRetired[ (m_nGlobalEpoch – 1) % EPOCH_COUNT ]   ; } } } ; 

リリヌスされたロックフリヌコンテナ芁玠は、削陀を埅機しおいるアむテムのスレッドロヌカルリストに配眮されたすm_arrRetired[m_nThreadEpoch % EPOCH_COUNT] 。 すべおのフロヌがm_nGlobalEpochグロヌバル゚ポックを通過するずすぐに、すべおのm_nGlobalEpoch – 1゚ポックフロヌのすべおのリストが解攟され、グロヌバル゚ポック自䜓が増分されたす。
UPD 2016
UPD 2016この疑䌌コヌドの゚ラヌを指摘しおくれたperfhunterに感謝したす。

「ロックフリヌデヌタ構造。」の蚘事の小さな゚ラヌを修正しおください。 内偎。 メモリヌ管理スキヌム」-exit関数の「゚ポックベヌスの再生」セクションで、m_arrRetired [m_nGlobalEpoch-2EPOCH_COUNT]をm_arrRetired [m_nGlobalEpoch-1EPOCH_COUNT]に眮き換えたす。 この時点で、ストリヌムのロヌカル時代はm_nGlobalEpochすでに入力したストリヌムの堎合enter、たたはm_nGlobalEpoch + 1クリティカルセクションに再床入力するストリヌムの堎合であり、生成m_nGlobalEpoch-1は安党に解攟できたす。



各ロックフリヌコンテナヌ操䜜は、クリティカルセクションに非垞に䌌おいるThreadEpoch::enter()およびThreadEpoch::exit()呌び出しに囲たれおいたす。
 lock_free_op( 
 ) { get_current_thread()->ThreadEpoch.enter() ; . . . // lock-free  . //    “ ” epoch-based , //     ,     ,   //  . . . . get_current_thread()->ThreadEpoch.exit() ; } 

このスキヌムは非垞に単玔で、ロヌカルリンク぀たり、コンテナヌ操䜜内のリンクを保護しお、コンテナヌ芁玠をロックフリヌにしたす。 スキヌムはコンテナヌの操䜜の倖郚からグロヌバルリンク保護を提䟛できたせん。぀たり、ロックフリヌコンテナヌの芁玠に察するむテレヌタヌの抂念は、゚ポックベヌスのスキヌムを䜿甚しお実装できたせん。 このスキヌムの欠点は、 すべおのプログラムフロヌが次の時代に移行する必芁があるこずです。぀たり、䜕らかのロックフリヌコンテナヌに移行する必芁がありたす。 少なくずも1぀のスレッドが次の時代に移行しない堎合、保留䞭のポむンタヌの削陀は発生したせん。 ストリヌムの優先床が同じでない堎合、優先床の䜎いストリヌムは、優先床の高いストリヌムの芁玠を削陀するために、遅延されたストリヌムのリストを無制限に増倧させる可胜性がありたす。 したがっお、゚ポックベヌスのスキヌムは、無制限のメモリ消費に぀ながる可胜性がありたすスレッドの1぀がクラッシュした堎合、確実に぀ながる。

libcdsラむブラリには、゚ポックベヌスのスキヌムの実装がありたせん。 理由すべおのフロヌがグロヌバル時代に到達したかどうかを刀断するための十分に効果的なアルゎリズムを構築できたせんでした。 おそらく読者の䞀人が解決策をお勧めしたすか..


ハザヌドポむンタヌ



このスキヌムはMichael [Mic02a、Mic03]によっお提案され、ロックフリヌデヌタ構造芁玠ぞのロヌカルリンクを保護するこずを目的ずしおいたす。 おそらく、これは最も有名で粟巧な遅延削陀のスキヌムです。 アトミックな読み取りず曞き蟌みのみに基づいおおり、CASのような「重い」同期プリミティブはたったく䜿甚したせん。
スキヌムの基瀎は、コンテナのロックフリヌ芁玠ぞのポむンタを、デヌタ構造のロックフリヌ操䜜内でハザヌド 危険ずしお宣蚀する矩務です。぀たり、芁玠ぞのポむンタを操䜜する前に、珟圚のストリヌムのハザヌドポむンタのHP配列に入れる必芁がありたす。 HPアレむは、ストリヌムに察しおプラむベヌトです。所有者ストリヌムのみがストリヌムに曞き蟌み、すべおのストリヌムは Scan手順で読み取るこずができたす。 さたざたなロックフリヌコンテナの動䜜を泚意深く分析するず、 HPアレむのサむズスレッドごずのハザヌドポむンタヌの数が3-4を超えないため、回路を維持するためのオヌバヌヘッドが小さいこずに気付くでしょう。
巚倧なデヌタ構造
公平には、64を超えるハザヌドポむンタヌを必芁ずする「巚倧な」デヌタ構造があるこずに泚意しおください。 䟋はskip-list cds::container::SkipListMap -確率的デヌタ構造、実際には各芁玠の可倉の高さを持぀リストのリストです。 libcdsにはこのスキヌムのスキップリスト実装がありたすが、このようなコンテナヌはハザヌドポむンタヌスキヌムにはあたり適しおいたせん。


ハザヌドポむンタヌ擬䌌コヌド[Mic02]
 //  // P :   // K :  hazard pointer    // N :   hazard pointers = K*P // R : batch size, RN=Ω(N), , R=2*N // Per-thread : //  Hazard Pouinter  //     - //    void * HP[K] //   dlist ( 0..R) unsigned dcount = 0; //      void* dlist[R]; //   //     dlist void RetireNode( void * node ) { dlist[dcount++] = node; //    –    Scan if (dcount == R) Scan(); } //   //     dlist,    //  Hazard Pointer void Scan() { unsigned i; unsigned p=0; unsigned new_dcount = 0; // 0 .. N void * hptr, plist[N], new_dlist[N]; // Stage 1 –    HP   //    plist   for (unsigned t=0; t < P; ++t) { void ** pHPThread = get_thread_data(t)->HP ; for (i = 0; i < K; ++i) { hptr = pHPThread[i]; if ( hptr != nullptr ) plist[p++] = hptr; } } // Stage 2 –  hazard pointer' //       sort(plist); // Stage 3 –  ,    hazard for ( i = 0; i < R; ++i ) { //  dlist[i]    plist  Hazard Pointer' //  dlist[i]    if ( binary_search(dlist[i], plist)) new_dlist[new_dcount++] = dlist[i]; else free(dlist[i]); } // Stage 4 –     . for (i = 0; i < new_dcount; ++i ) dlist[i] = new_dlist[i]; dcount = new_dcount; } 


ロックなしのpNodeコンテナ芁玠のRetireNode(pNode)を削陀するず、ストリヌムjはdlist保留削陀が必芁芁玠のdlistロヌカルストリヌムj リストにdlistたす。 dlistのサむズがRに達するず Rは N = P*Kに匹敵したすが、 Nを超えたす。たずえばR = 2N 、 Scan()プロシヌゞャが呌び出され、保留䞭のアむテムの削陀が行われたす。 条件R > P*Kは必須です。この条件が満たされた堎合にのみ、 Scan()が保留䞭のデヌタ配列からすべおを削陀できるこずが保蚌されたす。 この条件に違反するず、 Scan()は配列から䜕も削陀しない可胜性があり、アルゎリズム゚ラヌが発生したす。配列は完党にいっぱいですが、サむズを小さくするこずはできたせん。

Scan()は4぀のステヌゞで構成されたす。


Hazard Pointer, , :
 std::atomic<T *> atomicPtr ; 
 T * localPtr ; do { localPtr = atomicPtr.load(std::memory_order_relaxed); HP[i] = localPtr ; } while ( localPtr != atomicPtr.load(std::memory_order_acquire)); 

atomicPtr localPtr ( ) HP[i] HP hazard- . , , atomicPtr , , atomicPtr localPtr . , HP ( hazard) atomicPtr . Hazard Pointer' ( hazard), , .

Hazard Pointer (HP-) C++11 memory ordering [Tor08].
MSQueue Hazard Pointer
Lock-free - [MS98] Hazard Pointer. “” , libcds:
 template <typename T> class MSQueue { struct node { std::atomic<node *> next ; T data; node(): next(nullptr) {} node( T const& v): next(nullptr), data(v) {} }; std::atomic<node *> m_Head; std::atomic<node *> m_Tail; public: MSQueue() { node * p = new node; m_Head.store( p, std::memory_order_release ); m_Tail.store( p, std::memory_order_release ); } void enqueue( T const& data ) { node * pNew = new node( data ); while (true) { node * t = m_Tail.load(std::memory_order_relaxed); //    hazard. HP – thread-private  HP[0] = t; //  ,  m_Tail  ! if (t != m_Tail.load(std::memory_order_acquire) continue; node * next = t->next.load(std::memory_order_acquire); if (t != m_Tail) continue; if (next != nullptr) { // m_Tail     //  m_Tail m_Tail.compare_exchange_weak( t, next, std::memory_order_release); continue; } node * tmp = nullptr; if ( t->next.compare_exchange_strong( tmp, pNew, std::memory_order_release)) break; } m_Tail.compare_exchange_strong( t, pNew, std::memory_order_acq_rel ); HP[0] = nullptr; //  hazard pointer } bool dequeue(T& dest) { while true { node * h = m_Head.load(std::memory_order_relaxed); //  Hazard Pointer HP[0] = h; // ,  m_Head   if (h != m_Head.load(std::memory_order_acquire)) continue; node * t = m_Tail.load(std::memory_order_relaxed); node * next = h->next.load(std::memory_order_acquire); // head->next    Hazard Pointer HP[1] = next; //  m_Head  –    if (h != m_Head.load(std::memory_order_relaxed)) continue; if (next == nullptr) { //   HP[0] = nullptr; return false; } if (h == t) { //   enqueue –  m_Tail m_Tail.compare_exchange_strong( t, next, std::memory_order_release); continue; } dest = next->data; if ( m_Head.compare_exchange_strong(h, next, std::memory_order_release)) break; } //  Hazard Pointers HP[0] = nullptr; HP[1] = nullptr; //       //    . RetireNode(h); } }; 



Hazard Pointer ? ? , , — . : hazard pointer' K . – hazard pointer – , , , hazard- . , hazard-. – [Har01]. , HP- .
, HP- hazard-. , . libcds , , , HP-. , — Pass the Buck , — Hazard Pointer, hazard-. .

Hazard Pointer libcds




hazard pointer libcds. – Hazard Pointer Manager — , libcds.dll/.so. — Thread HP Manager , — HP hazard pointer' K () Retired R . Thread HP Manager . P . libcds:


libcds HP- :

«» , , , ? : (, retired) :
 struct retired_ptr { typedef void (* fnDisposer )( void * ); void * ptr ; //   fnDisposer pDisposer; // - retired_ptr( void * p, fnDisposer d): ptr(p), pDisposer(d) {} }; 

( retired ) . Scan() HP- pDisposer(ptr) «» .
pDisposer . . , :
 template <typename T> struct make_disposer { static void dispose( void * p ) { delete reinterpret_cast<T *>(p); } }; template <typename T> void retire_ptr( T * p ) { //  p   arrRetired     // ,  arrRetired –    arrRetired.push( retired_ptr( p, make_disposer<T>::dispose )); //    –  scan if ( arrRetired.full() ) scan(); } 

, , , .


HP- libcds, main() cds::gc::HP , , HP-, . , , cds::gc::HP . API HP-.

API cds::gc::HP
cds::gc::HP – , , .
  • コンストラクタヌ
     HP(size_t nHazardPtrCount = 0, size_t nMaxThreadCount = 0, size_t nMaxRetiredPtrCount = 0, cds::gc::hzp::scan_type nScanType = cds::gc::hzp::inplace); 

    nHazardPtrCount – hazard pointer' ( K )
    nMaxThreadCount – ( P )
    nMaxRetiredPtrCount – retired- ( R = 2KP )
    nScanType – : cds::gc::hzp::classic , Scan ; cds::gc::hzp::inplace Scan() new_dlist dlist ( ).

    , cds::gc::HP . , cds::gc::HP Hazard Pointer, , , .
  • retired ( )
     template <class Disposer, typename T> static void retire( T * p ) ; template <typename T> static void retire( T * p, void (* pFunc)(T *) ) 

    Disposer ( pFunc ) (). :
     struct Foo { 
 }; struct fooDisposer { void operator()( Foo * p ) const { delete p; } }; //   myDisposer    Foo Foo * p = new Foo ; cds::gc::HP::retire<fooDisposer>( p ); 

  •  static void force_dispose(); 

    Scan() Hazard Pointer. , , libcds .

, cds::gc::HP :
  • thread_gc – (thread data), Hazard Pointer. , HP- , ,
  • Guard – hazard pointer
  • template <size_t Count> GuardArray – hazard pointer'. HP- hazard- . , Guard

Guard GuardArray<N> Hazard Pointer, . .

Guard hazard- API:
  •  template <typename T> T protect( CDS_ATOMIC::atomic<T> const& toGuard ); template <typename T, class Func> T protect( CDS_ATOMIC::atomic<T> const& toGuard, Func f ); 

    ( T – ) hazard. , : toGuard , hazard pointer' , .
    ( Func ) , hazard T * , . , (node), (, node ). Func :
     struct functor { value_type * operator()( T * p ) ; }; 

    , hazard.
  •  template <typename T> T * assign( T * p ); template <typename T, int Bitmask> T * assign( cds::details::marked_ptr<T, Bitmask> p ); 

    p hazard. , protect , , — p hazard-.
    cds::details::marked_ptr . marked- 2-3 ( 0 ) , — lock-free . hazard- ( Bitmask ).
    , hazard.
  •  template <typename T> T * get() const; 

    hazard-. .
  •  void copy( Guard const& src ); 

    hazard- src this . hazard- .
  •  void clear(); 

    hazard-. Guard .

GuardArray API, :
 template <typename T> T protect(size_t nIndex, CDS_ATOMIC::atomic<T> const& toGuard ); template <typename T, class Func> T protect(size_t nIndex, CDS_ATOMIC::atomic<T> const& toGuard, Func f ) template <typename T> T * assign( size_t nIndex, T * p ); template <typename T, int Bitmask> T * assign( size_t nIndex, cds::details::marked_ptr<T, Bitmask> p ); void copy( size_t nDestIndex, size_t nSrcIndex ); void copy( size_t nIndex, Guard const& src ); template <typename T> T * get( size_t nIndex) const; void clear( size_t nIndex); 


CDS_ATOMIC – ?
, std::atomic . ( ) C++11 atomic , CDS_ATOMIC std . – cds::cxx11_atomics , , libcds- atomic. libcds boost.atomic , CDS_ATOMIC boost .


Hazard Pointer with Reference Counter



Hazard Pointer , lock-free . , , , , : hazard-.
, HP- . - HP-, . , (, hazard- ). , hazard-, , HP- .

— «, , ». , — , . , .

, , (reference counting, RefCount). Valois – lock-free — . RefCount- , – , . , RefCount-, lock-free fetch-and-add (, , – ).
2005 [GPST05], Hazard Pointer RefCount : Hazard Pointers lock-free , RefCount – . HRC (Hazard pointer RefCounting).
Hazard Pointers / , RefCounting- . , (- , . [GPST05]). Hazard Pointers lock-free , HRC :
 void CleanUpNode( Node * pNode); void TerminateNode( Node * pNode); 

TerminateNode pNode . CleanUpNode , , pNode «» ( ) , () ; RefCount- , , CleanUpNode :
 void CleanUpNode(Node * pNode) { for (all x where pNode->link[x] of node is reference-counted) { retry: node1 = DeRefLink(&pNode->link[x]); //  HP if (node1 != NULL and !is_deleted( node1 )) { node2 = DeRefLink(node1->link[x]); //  HP //  ,       //    node1 CompareAndSwapRef(&pNode->link[x],node1,node2); ReleaseRef(node2); //  HP ReleaseRef(node1); //  HP goto retry; //       ,   } ReleaseRef(node1); //  HP } } 

lock-free ( C++) HRC lock-free . , , CleanUpNode , , . lock-free , , MultiCAS .
, Hazard Pointers , . Scan Hazar Pointers ( , CleanUpNode ). : Hazard Pointers ( R > N = P * K ), Scan - ( , hazard-), HRC Scan - ( – ). , Scan , CleanUpAll : CleanUpNode , Scan .
HRC- libcds
UPD (2016 ): libcds 2.0.0 HRC- ( , ), - , Hazard Pointer.

libcds HRC- HP-. – cds::gc::HRC . API API cds::gc::HP . namespace cds::gc::hrc .
HRC- – – libcds. , lock-free . , , . – – lock-free : -, , . , , HP- , , — lock-free .
, HRC- libcds HP-. , ( ) HP-: HRC- 2-3 , HP-. «», : - Scan (, - ) CleanUpAll , retired-.
HRC- libcds HP-like , . HRC- HRC-based HP-based .


Pass the Buck



lock-free , Herlihy & al Pass-the-Buck (PTB, “ ”) [HLM02, HLM05], HP- ., .
, HP-, PTB- (guarded, hazard pointer HP-). PTB- ( hazard pointer'). (retired) Liberate — Scan HP-. Liberate , . HP-, retired- , PTB- .
guard' (hazard pointer'): , retired-, hand-off (“ ”). Liberate , retired- guard', retired- hand-off guard'. Liberate hand-off , guard, , , .

[HLM05] Liberate : wait-free lock-free. Wait-free dwCAS (CAS ), dwCAS . Lock-free , . (guard' retired-) lock-free Liberate , (, retired-, ). , PTB- , Liberate .

, Liberate PTB-, HP-. PTB libcds HP- hazard- retired-. : «» HP- , PTB, PTB guard'.

libcds
UPD (2016 ): libcds 2.0.0 cds::gc::DHP — HP, — pass-the-buck , — Hazard Pointer .

libcds PTB- cds::gc::PTB , namespace cds::gc::ptb . API cds::gc::PTB cds::gc:::HP , . :
 PTB( size_t nLiberateThreshold = 1024, size_t nInitialThreadGuardCount = 8 ); 

  • nLiberateThreshold — Liberate . retired- , Liberate
  • nInitialThreadGuardCount — quard' (, libcds). guard'



おわりに


safe memory reclamation, Hazard Pointer. HP- lock-free .

lock-free . libcds, , (attach) GC libcds. , Scan() / Liberate() . — .

— RCU, HP-like , — .

UPD 2016: Errandir , Hazard Pointer ( HP).

[Fra03] Keir Fraser Practical Lock Freedom , 2004; technical report is based on a dissertation submitted September 2003 by K.Fraser for the degree of Doctor of Philosophy to the University of Cambridge, King's College

[GPST05] Anders Gidenstam, Marina Papatriantafilou, Hakan Sundell, Philippas Tsigas Practical and Efficient Lock-Free Garbage Collection Based on Reference Counting , Technical Report no. 2005-04 in Computer Science and Engineering at Chalmers University of Technology and Goteborg University, 2005

[Har01] Timothy Harris A pragmatic implementation of Non-Blocking Linked List , 2001

[HLM02] M. Herlihy, V. Luchangco, and M. Moir The repeat offender problem: A mechanism for supporting
dynamic-sized lockfree data structures
Technical Report TR-2002-112, Sun Microsystems
Laboratories, 2002.

[HLM05] M.Herlihy, V.Luchangco, P.Martin, and M.Moir Nonblocing Memory Management Support for Dynamic-Sized Data Structure , ACM Transactions on Computer Systems, Vol. 23, No. 2, May 2005, Pages 146–196.

[Mic02] Maged Michael Safe Memory Reclamation for Dynamic Lock-Free Objects Using Atomic Reads and Writes , 2002

[Mic03] Maged Michael Hazard Pointers: Safe Memory Reclamation for Lock-Free Objects , 2003

[MS98] Maged Michael, Michael Scott Simple, Fast and Practical Non-Bloking and Blocking Concurrent Queue Algorithms , 1998

[Tor08] Johan Torp The parallelism shift and C++'s memory model , chapter 13, 2008


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


All Articles