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



以前のメモで述べたように、ロックフリーデータ構造を実装する際の主な問題は、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





ロックフリーのデータ構造
開始する

基本:



中:



外から:






All Articles