3 #ifndef CDSLIB_INTRUSIVE_IMPL_SKIP_LIST_H
4 #define CDSLIB_INTRUSIVE_IMPL_SKIP_LIST_H
8 #include <functional> // ref
9 #include <cds/intrusive/details/skip_list_base.h>
10 #include <cds/opt/compare.h>
11 #include <cds/details/binary_functor_wrapper.h>
13 namespace cds { namespace intrusive {
16 namespace skip_list { namespace details {
18 template <class GC, typename NodeTraits, typename BackOff, bool IsConst>
22 typedef NodeTraits node_traits;
23 typedef BackOff back_off;
24 typedef typename node_traits::node_type node_type;
25 typedef typename node_traits::value_type value_type;
26 static CDS_CONSTEXPR bool const c_isConst = IsConst;
28 typedef typename std::conditional< c_isConst, value_type const&, value_type&>::type value_ref;
31 typedef typename node_type::marked_ptr marked_ptr;
32 typedef typename node_type::atomic_marked_ptr atomic_marked_ptr;
34 typename gc::Guard m_guard;
38 static value_type * gc_protect( marked_ptr p )
40 return node_traits::to_value_ptr( p.ptr() );
50 if ( m_pNode->next( m_pNode->height() - 1 ).load( atomics::memory_order_acquire ).bits() ) {
51 // Current node is marked as deleted. So, its next pointer can point to anything
52 // In this case we interrupt our iteration and returns end() iterator.
57 marked_ptr p = m_guard.protect( (*m_pNode)[0], gc_protect );
58 node_type * pp = p.ptr();
60 // p is marked as deleted. Spin waiting for physical removal
64 else if ( pp && pp->next( pp->height() - 1 ).load( atomics::memory_order_relaxed ).bits() ) {
65 // p is marked as deleted. Spin waiting for physical removal
75 public: // for internal use only!!!
76 iterator( node_type& refHead )
82 marked_ptr p = m_guard.protect( refHead[0], gc_protect );
89 node_type * pp = p.ptr();
90 // Logically deleted node is marked from highest level
91 if ( !pp->next( pp->height() - 1 ).load( atomics::memory_order_acquire ).bits() ) {
105 iterator( iterator const& s)
106 : m_pNode( s.m_pNode )
108 m_guard.assign( node_traits::to_value_ptr(m_pNode) );
111 value_type * operator ->() const
113 assert( m_pNode != nullptr );
114 assert( node_traits::to_value_ptr( m_pNode ) != nullptr );
116 return node_traits::to_value_ptr( m_pNode );
119 value_ref operator *() const
121 assert( m_pNode != nullptr );
122 assert( node_traits::to_value_ptr( m_pNode ) != nullptr );
124 return *node_traits::to_value_ptr( m_pNode );
128 iterator& operator ++()
134 iterator& operator =(const iterator& src)
136 m_pNode = src.m_pNode;
137 m_guard.copy( src.m_guard );
141 template <typename Bkoff, bool C>
142 bool operator ==(iterator<gc, node_traits, Bkoff, C> const& i ) const
144 return m_pNode == i.m_pNode;
146 template <typename Bkoff, bool C>
147 bool operator !=(iterator<gc, node_traits, Bkoff, C> const& i ) const
149 return !( *this == i );
152 }} // namespace skip_list::details
155 /// Lock-free skip-list set
156 /** @ingroup cds_intrusive_map
157 @anchor cds_intrusive_SkipListSet_hp
159 The implementation of well-known probabilistic data structure called skip-list
160 invented by W.Pugh in his papers:
161 - [1989] W.Pugh Skip Lists: A Probabilistic Alternative to Balanced Trees
162 - [1990] W.Pugh A Skip List Cookbook
164 A skip-list is a probabilistic data structure that provides expected logarithmic
165 time search without the need of rebalance. The skip-list is a collection of sorted
166 linked list. Nodes are ordered by key. Each node is linked into a subset of the lists.
167 Each list has a level, ranging from 0 to 32. The bottom-level list contains
168 all the nodes, and each higher-level list is a sublist of the lower-level lists.
169 Each node is created with a random top level (with a random height), and belongs
170 to all lists up to that level. The probability that a node has the height 1 is 1/2.
171 The probability that a node has the height N is 1/2 ** N (more precisely,
172 the distribution depends on an random generator provided, but our generators
175 The lock-free variant of skip-list is implemented according to book
176 - [2008] M.Herlihy, N.Shavit "The Art of Multiprocessor Programming",
177 chapter 14.4 "A Lock-Free Concurrent Skiplist".
179 <b>Template arguments</b>:
180 - \p GC - Garbage collector used. Note the \p GC must be the same as the GC used for item type \p T, see \p skip_list::node.
181 - \p T - type to be stored in the list. The type must be based on \p skip_list::node (for \p skip_list::base_hook)
182 or it must have a member of type \p skip_list::node (for \p skip_list::member_hook).
183 - \p Traits - skip-list traits, default is \p skip_list::traits.
184 It is possible to declare option-based list with \p cds::intrusive::skip_list::make_traits metafunction istead of \p Traits
187 @warning The skip-list requires up to 67 hazard pointers that may be critical for some GCs for which
188 the guard count is limited (like as \p gc::HP). Those GCs should be explicitly initialized with
189 hazard pointer enough: \code cds::gc::HP myhp( 67 ) \endcode. Otherwise an run-time exception may be raised
190 when you try to create skip-list object.
192 There are several specializations of \p %SkipListSet for each \p GC. You should include:
193 - <tt><cds/intrusive/skip_list_hp.h></tt> for \p gc::HP garbage collector
194 - <tt><cds/intrusive/skip_list_dhp.h></tt> for \p gc::DHP garbage collector
195 - <tt><cds/intrusive/skip_list_nogc.h></tt> for \ref cds_intrusive_SkipListSet_nogc for append-only set
196 - <tt><cds/intrusive/skip_list_rcu.h></tt> for \ref cds_intrusive_SkipListSet_rcu "RCU type"
200 The class supports a forward iterator (\ref iterator and \ref const_iterator).
201 The iteration is ordered.
202 The iterator object is thread-safe: the element pointed by the iterator object is guarded,
203 so, the element cannot be reclaimed while the iterator object is alive.
204 However, passing an iterator object between threads is dangerous.
206 @warning Due to concurrent nature of skip-list set it is not guarantee that you can iterate
207 all elements in the set: any concurrent deletion can exclude the element
208 pointed by the iterator from the set, and your iteration can be terminated
209 before end of the set. Therefore, such iteration is more suitable for debugging purpose only
211 Remember, each iterator object requires 2 additional hazard pointers, that may be
212 a limited resource for \p GC like as \p gc::HP (for \p gc::DHP the count of
213 guards is unlimited).
215 The iterator class supports the following minimalistic interface:
222 iterator( iterator const& s);
224 value_type * operator ->() const;
225 value_type& operator *() const;
228 iterator& operator ++();
231 iterator& operator = (const iterator& src);
233 bool operator ==(iterator const& i ) const;
234 bool operator !=(iterator const& i ) const;
237 Note, the iterator object returned by \p end(), \p cend() member functions points to \p nullptr and should not be dereferenced.
241 You should incorporate \p skip_list::node into your struct \p T and provide
242 appropriate \p skip_list::traits::hook in your \p Traits template parameters. Usually, for \p Traits you
243 define a struct based on \p skip_list::traits.
245 Example for \p gc::HP and base hook:
247 // Include GC-related skip-list specialization
248 #include <cds/intrusive/skip_list_hp.h>
250 // Data stored in skip list
251 struct my_data: public cds::intrusive::skip_list::node< cds::gc::HP >
260 // my_data compare functor
262 int operator()( const my_data& d1, const my_data& d2 )
264 return d1.strKey.compare( d2.strKey );
267 int operator()( const my_data& d, const std::string& s )
269 return d.strKey.compare(s);
272 int operator()( const std::string& s, const my_data& d )
274 return s.compare( d.strKey );
279 // Declare your traits
280 struct my_traits: public cds::intrusive::skip_list::traits
282 typedef cds::intrusive::skip_list::base_hook< cds::opt::gc< cds::gc::HP > > hook;
283 typedef my_data_cmp compare;
286 // Declare skip-list set type
287 typedef cds::intrusive::SkipListSet< cds::gc::HP, my_data, my_traits > traits_based_set;
290 Equivalent option-based code:
292 // GC-related specialization
293 #include <cds/intrusive/skip_list_hp.h>
302 // Declare option-based skip-list set
303 typedef cds::intrusive::SkipListSet< cds::gc::HP
305 , typename cds::intrusive::skip_list::make_traits<
306 cds::intrusive::opt::hook< cds::intrusive::skip_list::base_hook< cds::opt::gc< cds::gc::HP > > >
307 ,cds::intrusive::opt::compare< my_data_cmp >
316 #ifdef CDS_DOXYGEN_INVOKED
317 ,typename Traits = skip_list::traits
325 typedef GC gc; ///< Garbage collector
326 typedef T value_type; ///< type of value stored in the skip-list
327 typedef Traits traits; ///< Traits template parameter
329 typedef typename traits::hook hook; ///< hook type
330 typedef typename hook::node_type node_type; ///< node type
332 # ifdef CDS_DOXYGEN_INVOKED
333 typedef implementation_defined key_comparator ; ///< key comparison functor based on opt::compare and opt::less option setter.
335 typedef typename opt::details::make_comparator< value_type, traits >::type key_comparator;
338 typedef typename traits::disposer disposer; ///< item disposer
339 typedef typename get_node_traits< value_type, node_type, hook>::type node_traits; ///< node traits
341 typedef typename traits::item_counter item_counter; ///< Item counting policy
342 typedef typename traits::memory_model memory_model; ///< Memory ordering, see \p cds::opt::memory_model option
343 typedef typename traits::random_level_generator random_level_generator; ///< random level generator
344 typedef typename traits::allocator allocator_type; ///< allocator for maintaining array of next pointers of the node
345 typedef typename traits::back_off back_off; ///< Back-off strategy
346 typedef typename traits::stat stat; ///< internal statistics type
349 typedef typename gc::template guarded_ptr< value_type > guarded_ptr; ///< Guarded pointer
351 /// Max node height. The actual node height should be in range <tt>[0 .. c_nMaxHeight)</tt>
353 The max height is specified by \ref skip_list::random_level_generator "random level generator" constant \p m_nUpperBound
354 but it should be no more than 32 (\p skip_list::c_nHeightLimit).
356 static unsigned int const c_nMaxHeight = std::conditional<
357 (random_level_generator::c_nUpperBound <= skip_list::c_nHeightLimit),
358 std::integral_constant< unsigned int, random_level_generator::c_nUpperBound >,
359 std::integral_constant< unsigned int, skip_list::c_nHeightLimit >
363 static unsigned int const c_nMinHeight = 5;
367 typedef typename node_type::atomic_marked_ptr atomic_node_ptr; ///< Atomic marked node pointer
368 typedef typename node_type::marked_ptr marked_node_ptr; ///< Node marked pointer
372 typedef skip_list::details::intrusive_node_builder< node_type, atomic_node_ptr, allocator_type > intrusive_node_builder;
374 typedef typename std::conditional<
375 std::is_same< typename traits::internal_node_builder, cds::opt::none >::value
376 ,intrusive_node_builder
377 ,typename traits::internal_node_builder
378 >::type node_builder;
380 typedef std::unique_ptr< node_type, typename node_builder::node_disposer > scoped_node_ptr;
382 // c_nMaxHeight * 2 - pPred/pSucc guards
383 // + 1 - for erase, unlink
385 static size_t const c_nHazardPtrCount = c_nMaxHeight * 2 + 2;
387 node_type * pPrev[ c_nMaxHeight ];
388 node_type * pSucc[ c_nMaxHeight ];
390 typename gc::template GuardArray< c_nMaxHeight * 2 > guards; ///< Guards array for pPrev/pSucc
391 node_type * pCur; // guarded by guards; needed only for \p update()
396 skip_list::details::head_node< node_type > m_Head; ///< head tower (max height)
398 item_counter m_ItemCounter; ///< item counter
399 random_level_generator m_RandomLevelGen; ///< random level generator instance
400 atomics::atomic<unsigned int> m_nHeight; ///< estimated high level
401 mutable stat m_Stat; ///< internal statistics
405 unsigned int random_level()
407 // Random generator produces a number from range [0..31]
408 // We need a number from range [1..32]
409 return m_RandomLevelGen() + 1;
412 template <typename Q>
413 node_type * build_node( Q v )
415 return node_builder::make_tower( v, m_RandomLevelGen );
418 static value_type * gc_protect( marked_node_ptr p )
420 return node_traits::to_value_ptr( p.ptr() );
423 static void dispose_node( value_type * pVal )
425 assert( pVal != nullptr );
426 typename node_builder::node_disposer()( node_traits::to_node_ptr(pVal) );
430 template <typename Q, typename Compare >
431 bool find_position( Q const& val, position& pos, Compare cmp, bool bStopIfFound )
434 marked_node_ptr pSucc;
435 marked_node_ptr pCur;
437 // Hazard pointer array:
438 // pPred: [nLevel * 2]
439 // pSucc: [nLevel * 2 + 1]
442 pPred = m_Head.head();
445 for ( int nLevel = static_cast<int>( c_nMaxHeight - 1 ); nLevel >= 0; --nLevel ) {
446 pos.guards.assign( nLevel * 2, node_traits::to_value_ptr( pPred ));
448 pCur = pos.guards.protect( nLevel * 2 + 1, pPred->next( nLevel ), gc_protect );
450 // pCur.bits() means that pPred is logically deleted
454 if ( pCur.ptr() == nullptr ) {
455 // end of the list at level nLevel - goto next level
459 // pSucc contains deletion mark for pCur
460 pSucc = pCur->next( nLevel ).load( memory_model::memory_order_acquire );
462 if ( pPred->next( nLevel ).load( memory_model::memory_order_acquire ).all() != pCur.ptr() )
465 if ( pSucc.bits() ) {
466 // pCur is marked, i.e. logically deleted.
467 marked_node_ptr p( pCur.ptr() );
468 if ( pPred->next( nLevel ).compare_exchange_strong( p, marked_node_ptr( pSucc.ptr() ),
469 memory_model::memory_order_acquire, atomics::memory_order_relaxed ))
472 gc::retire( node_traits::to_value_ptr( pCur.ptr() ), dispose_node );
473 m_Stat.onEraseWhileFind();
479 nCmp = cmp( *node_traits::to_value_ptr( pCur.ptr()), val );
482 pos.guards.copy( nLevel * 2, nLevel * 2 + 1 ) ; // pPrev guard := cur guard
484 else if ( nCmp == 0 && bStopIfFound )
492 pos.pPrev[ nLevel ] = pPred;
493 pos.pSucc[ nLevel ] = pCur.ptr();
500 pos.pCur = pCur.ptr();
501 return pCur.ptr() && nCmp == 0;
504 bool find_min_position( position& pos )
507 marked_node_ptr pSucc;
508 marked_node_ptr pCur;
510 // Hazard pointer array:
511 // pPred: [nLevel * 2]
512 // pSucc: [nLevel * 2 + 1]
515 pPred = m_Head.head();
517 for ( int nLevel = static_cast<int>( c_nMaxHeight - 1 ); nLevel >= 0; --nLevel ) {
518 pos.guards.assign( nLevel * 2, node_traits::to_value_ptr( pPred ));
519 pCur = pos.guards.protect( nLevel * 2 + 1, pPred->next( nLevel ), gc_protect );
521 // pCur.bits() means that pPred is logically deleted
522 // head cannot be deleted
523 assert( pCur.bits() == 0 );
527 // pSucc contains deletion mark for pCur
528 pSucc = pCur->next( nLevel ).load( memory_model::memory_order_acquire );
530 if ( pPred->next( nLevel ).load( memory_model::memory_order_acquire ).all() != pCur.ptr() )
533 if ( pSucc.bits() ) {
534 // pCur is marked, i.e. logically deleted.
535 marked_node_ptr p( pCur.ptr() );
536 if ( pPred->next( nLevel ).compare_exchange_strong( p, marked_node_ptr( pSucc.ptr() ),
537 memory_model::memory_order_acquire, atomics::memory_order_relaxed ))
540 gc::retire( node_traits::to_value_ptr( pCur.ptr() ), dispose_node );
541 m_Stat.onEraseWhileFind();
549 pos.pPrev[ nLevel ] = pPred;
550 pos.pSucc[ nLevel ] = pCur.ptr();
553 return (pos.pCur = pCur.ptr()) != nullptr;
556 bool find_max_position( position& pos )
559 marked_node_ptr pSucc;
560 marked_node_ptr pCur;
562 // Hazard pointer array:
563 // pPred: [nLevel * 2]
564 // pSucc: [nLevel * 2 + 1]
567 pPred = m_Head.head();
569 for ( int nLevel = static_cast<int>( c_nMaxHeight - 1 ); nLevel >= 0; --nLevel ) {
570 pos.guards.assign( nLevel * 2, node_traits::to_value_ptr( pPred ));
572 pCur = pos.guards.protect( nLevel * 2 + 1, pPred->next( nLevel ), gc_protect );
574 // pCur.bits() means that pPred is logically deleted
578 if ( pCur.ptr() == nullptr ) {
579 // end of the list at level nLevel - goto next level
583 // pSucc contains deletion mark for pCur
584 pSucc = pCur->next( nLevel ).load( memory_model::memory_order_acquire );
586 if ( pPred->next( nLevel ).load( memory_model::memory_order_acquire ).all() != pCur.ptr() )
589 if ( pSucc.bits() ) {
590 // pCur is marked, i.e. logically deleted.
591 marked_node_ptr p( pCur.ptr() );
592 if ( pPred->next( nLevel ).compare_exchange_strong( p, marked_node_ptr( pSucc.ptr() ),
593 memory_model::memory_order_acquire, atomics::memory_order_relaxed ))
596 gc::retire( node_traits::to_value_ptr( pCur.ptr() ), dispose_node );
597 m_Stat.onEraseWhileFind();
607 pos.guards.copy( nLevel * 2, nLevel * 2 + 1 ); // pPrev guard := cur guard
608 //pos.guards.copy( nLevel * 2, gCur ) ; // pPrev guard := gCur
613 pos.pPrev[ nLevel ] = pPred;
614 pos.pSucc[ nLevel ] = pCur.ptr();
617 return (pos.pCur = pCur.ptr()) != nullptr;
620 template <typename Func>
621 bool insert_at_position( value_type& val, node_type * pNode, position& pos, Func f )
623 unsigned int nHeight = pNode->height();
625 for ( unsigned int nLevel = 1; nLevel < nHeight; ++nLevel )
626 pNode->next(nLevel).store( marked_node_ptr(), memory_model::memory_order_relaxed );
630 marked_node_ptr p( pos.pSucc[0] );
631 pNode->next( 0 ).store( p, memory_model::memory_order_release );
632 if ( !pos.pPrev[0]->next(0).compare_exchange_strong( p, marked_node_ptr(pNode), memory_model::memory_order_release, atomics::memory_order_relaxed ))
638 // Insert at level 1..max
639 for ( unsigned int nLevel = 1; nLevel < nHeight; ++nLevel ) {
642 marked_node_ptr q( pos.pSucc[ nLevel ]);
643 if ( !pNode->next( nLevel ).compare_exchange_strong( p, q, memory_model::memory_order_release, atomics::memory_order_relaxed )) {
644 // pNode has been marked as removed while we are inserting it
647 m_Stat.onLogicDeleteWhileInsert();
651 if ( pos.pPrev[nLevel]->next(nLevel).compare_exchange_strong( q, marked_node_ptr( pNode ), memory_model::memory_order_release, atomics::memory_order_relaxed ))
654 // Renew insert position
655 m_Stat.onRenewInsertPosition();
656 if ( !find_position( val, pos, key_comparator(), false )) {
657 // The node has been deleted while we are inserting it
658 m_Stat.onNotFoundWhileInsert();
666 template <typename Func>
667 bool try_remove_at( node_type * pDel, position& pos, Func f )
669 assert( pDel != nullptr );
671 marked_node_ptr pSucc;
673 // logical deletion (marking)
674 for ( unsigned int nLevel = pDel->height() - 1; nLevel > 0; --nLevel ) {
676 pSucc = pDel->next(nLevel);
677 if ( pSucc.bits() || pDel->next(nLevel).compare_exchange_weak( pSucc, pSucc | 1,
678 memory_model::memory_order_release, atomics::memory_order_relaxed ))
686 marked_node_ptr p( pDel->next(0).load(memory_model::memory_order_relaxed).ptr() );
687 if ( pDel->next(0).compare_exchange_strong( p, p | 1, memory_model::memory_order_release, atomics::memory_order_relaxed ))
689 f( *node_traits::to_value_ptr( pDel ));
694 for ( int nLevel = static_cast<int>( pDel->height() - 1 ); nLevel >= 0; --nLevel ) {
695 pSucc = pDel->next(nLevel).load(memory_model::memory_order_relaxed);
696 if ( !pos.pPrev[nLevel]->next(nLevel).compare_exchange_strong( p, marked_node_ptr(pSucc.ptr()),
697 memory_model::memory_order_acquire, atomics::memory_order_relaxed) )
700 find_position( *node_traits::to_value_ptr( pDel ), pos, key_comparator(), false );
701 m_Stat.onSlowErase();
706 // Fast erasing success
707 gc::retire( node_traits::to_value_ptr( pDel ), dispose_node );
708 m_Stat.onFastErase();
713 // Another thread is deleting pDel right now
717 m_Stat.onEraseRetry();
721 enum finsd_fastpath_result {
723 find_fastpath_not_found,
726 template <typename Q, typename Compare, typename Func>
727 finsd_fastpath_result find_fastpath( Q& val, Compare cmp, Func f )
730 typename gc::template GuardArray<2> guards;
731 marked_node_ptr pCur;
732 marked_node_ptr pNull;
736 pPred = m_Head.head();
737 for ( int nLevel = static_cast<int>( m_nHeight.load(memory_model::memory_order_relaxed) - 1 ); nLevel >= 0; --nLevel ) {
738 pCur = guards.protect( 1, pPred->next(nLevel), gc_protect );
742 while ( pCur != pNull ) {
744 unsigned int nAttempt = 0;
745 while ( pCur.bits() && nAttempt++ < 16 ) {
747 pCur = guards.protect( 1, pPred->next(nLevel), gc_protect );
752 // Maybe, we are on deleted node sequence
753 // Abort searching, try slow-path
754 return find_fastpath_abort;
759 int nCmp = cmp( *node_traits::to_value_ptr( pCur.ptr() ), val );
763 pCur = guards.protect( 1, pCur->next(nLevel), gc_protect );
765 else if ( nCmp == 0 ) {
767 f( *node_traits::to_value_ptr( pCur.ptr() ), val );
768 return find_fastpath_found;
770 else // pCur > val - go down
776 return find_fastpath_not_found;
779 template <typename Q, typename Compare, typename Func>
780 bool find_slowpath( Q& val, Compare cmp, Func f )
783 if ( find_position( val, pos, cmp, true )) {
784 assert( cmp( *node_traits::to_value_ptr( pos.pCur ), val ) == 0 );
786 f( *node_traits::to_value_ptr( pos.pCur ), val );
793 template <typename Q, typename Compare, typename Func>
794 bool find_with_( Q& val, Compare cmp, Func f )
796 switch ( find_fastpath( val, cmp, f )) {
797 case find_fastpath_found:
798 m_Stat.onFindFastSuccess();
800 case find_fastpath_not_found:
801 m_Stat.onFindFastFailed();
807 if ( find_slowpath( val, cmp, f )) {
808 m_Stat.onFindSlowSuccess();
812 m_Stat.onFindSlowFailed();
816 template <typename Q, typename Compare>
817 bool get_with_( typename guarded_ptr::native_guard& guard, Q const& val, Compare cmp )
819 return find_with_( val, cmp, [&guard](value_type& found, Q const& ) { guard.set(&found); } );
822 template <typename Q, typename Compare, typename Func>
823 bool erase_( Q const& val, Compare cmp, Func f )
827 if ( !find_position( val, pos, cmp, false ) ) {
828 m_Stat.onEraseFailed();
832 node_type * pDel = pos.pCur;
833 typename gc::Guard gDel;
834 gDel.assign( node_traits::to_value_ptr(pDel) );
835 assert( cmp( *node_traits::to_value_ptr( pDel ), val ) == 0 );
837 unsigned int nHeight = pDel->height();
838 if ( try_remove_at( pDel, pos, f )) {
840 m_Stat.onRemoveNode( nHeight );
841 m_Stat.onEraseSuccess();
845 m_Stat.onEraseFailed();
849 template <typename Q, typename Compare>
850 bool extract_( typename guarded_ptr::native_guard& guard, Q const& val, Compare cmp )
855 if ( !find_position( val, pos, cmp, false ) ) {
856 m_Stat.onExtractFailed();
861 node_type * pDel = pos.pCur;
862 guard.set( node_traits::to_value_ptr(pDel));
863 assert( cmp( *node_traits::to_value_ptr( pDel ), val ) == 0 );
865 unsigned int nHeight = pDel->height();
866 if ( try_remove_at( pDel, pos, [](value_type const&) {} )) {
868 m_Stat.onRemoveNode( nHeight );
869 m_Stat.onExtractSuccess();
872 m_Stat.onExtractRetry();
876 bool extract_min_( typename guarded_ptr::native_guard& gDel )
881 if ( !find_min_position( pos ) ) {
883 m_Stat.onExtractMinFailed();
888 node_type * pDel = pos.pCur;
890 unsigned int nHeight = pDel->height();
891 gDel.set( node_traits::to_value_ptr(pDel) );
893 if ( try_remove_at( pDel, pos, [](value_type const&) {} )) {
895 m_Stat.onRemoveNode( nHeight );
896 m_Stat.onExtractMinSuccess();
900 m_Stat.onExtractMinRetry();
904 bool extract_max_( typename guarded_ptr::native_guard& gDel )
909 if ( !find_max_position( pos ) ) {
911 m_Stat.onExtractMaxFailed();
916 node_type * pDel = pos.pCur;
918 unsigned int nHeight = pDel->height();
919 gDel.set( node_traits::to_value_ptr(pDel) );
921 if ( try_remove_at( pDel, pos, [](value_type const&) {} )) {
923 m_Stat.onRemoveNode( nHeight );
924 m_Stat.onExtractMaxSuccess();
928 m_Stat.onExtractMaxRetry();
932 void increase_height( unsigned int nHeight )
934 unsigned int nCur = m_nHeight.load( memory_model::memory_order_relaxed );
935 if ( nCur < nHeight )
936 m_nHeight.compare_exchange_strong( nCur, nHeight, memory_model::memory_order_release, atomics::memory_order_relaxed );
941 /// Default constructor
943 The constructor checks whether the count of guards is enough
944 for skip-list and may raise an exception if not.
947 : m_Head( c_nMaxHeight )
948 , m_nHeight( c_nMinHeight )
950 static_assert( (std::is_same< gc, typename node_type::gc >::value), "GC and node_type::gc must be the same type" );
952 gc::check_available_guards( c_nHazardPtrCount );
954 // Barrier for head node
955 atomics::atomic_thread_fence( memory_model::memory_order_release );
958 /// Clears and destructs the skip-list
966 typedef skip_list::details::iterator< gc, node_traits, back_off, false > iterator;
968 /// Const iterator type
969 typedef skip_list::details::iterator< gc, node_traits, back_off, true > const_iterator;
971 /// Returns a forward iterator addressing the first element in a set
974 return iterator( *m_Head.head() );
977 /// Returns a forward const iterator addressing the first element in a set
978 const_iterator begin() const
980 return const_iterator( *m_Head.head() );
982 /// Returns a forward const iterator addressing the first element in a set
983 const_iterator cbegin() const
985 return const_iterator( *m_Head.head() );
988 /// Returns a forward iterator that addresses the location succeeding the last element in a set.
994 /// Returns a forward const iterator that addresses the location succeeding the last element in a set.
995 const_iterator end() const
997 return const_iterator();
999 /// Returns a forward const iterator that addresses the location succeeding the last element in a set.
1000 const_iterator cend() const
1002 return const_iterator();
1006 /// Inserts new node
1008 The function inserts \p val in the set if it does not contain
1009 an item with key equal to \p val.
1011 Returns \p true if \p val is placed into the set, \p false otherwise.
1013 bool insert( value_type& val )
1015 return insert( val, []( value_type& ) {} );
1018 /// Inserts new node
1020 This function is intended for derived non-intrusive containers.
1022 The function allows to split creating of new item into two part:
1023 - create item with key only
1024 - insert new item into the set
1025 - if inserting is success, calls \p f functor to initialize value-field of \p val.
1027 The functor signature is:
1029 void func( value_type& val );
1031 where \p val is the item inserted. User-defined functor \p f should guarantee that during changing
1032 \p val no any other changes could be made on this set's item by concurrent threads.
1033 The user-defined functor is called only if the inserting is success.
1035 template <typename Func>
1036 bool insert( value_type& val, Func f )
1038 typename gc::Guard gNew;
1039 gNew.assign( &val );
1041 node_type * pNode = node_traits::to_node_ptr( val );
1042 scoped_node_ptr scp( pNode );
1043 unsigned int nHeight = pNode->height();
1044 bool bTowerOk = nHeight > 1 && pNode->get_tower() != nullptr;
1045 bool bTowerMade = false;
1050 if ( find_position( val, pos, key_comparator(), true )) {
1051 // scoped_node_ptr deletes the node tower if we create it
1055 m_Stat.onInsertFailed();
1060 build_node( pNode );
1061 nHeight = pNode->height();
1066 if ( !insert_at_position( val, pNode, pos, f )) {
1067 m_Stat.onInsertRetry();
1071 increase_height( nHeight );
1073 m_Stat.onAddNode( nHeight );
1074 m_Stat.onInsertSuccess();
1080 /// Updates the node
1082 The operation performs inserting or changing data with lock-free manner.
1084 If the item \p val is not found in the set, then \p val is inserted into the set
1085 iff \p bInsert is \p true.
1086 Otherwise, the functor \p func is called with item found.
1087 The functor \p func signature is:
1089 void func( bool bNew, value_type& item, value_type& val );
1092 - \p bNew - \p true if the item has been inserted, \p false otherwise
1093 - \p item - item of the set
1094 - \p val - argument \p val passed into the \p %update() function
1095 If new item has been inserted (i.e. \p bNew is \p true) then \p item and \p val arguments
1096 refer to the same thing.
1098 Returns std::pair<bool, bool> where \p first is \p true if operation is successfull,
1099 i.e. the node has been inserted or updated,
1100 \p second is \p true if new item has been added or \p false if the item with \p key
1103 @warning See \ref cds_intrusive_item_creating "insert item troubleshooting"
1105 template <typename Func>
1106 std::pair<bool, bool> update( value_type& val, Func func, bool bInsert = true )
1108 typename gc::Guard gNew;
1109 gNew.assign( &val );
1111 node_type * pNode = node_traits::to_node_ptr( val );
1112 scoped_node_ptr scp( pNode );
1113 unsigned int nHeight = pNode->height();
1114 bool bTowerOk = nHeight > 1 && pNode->get_tower() != nullptr;
1115 bool bTowerMade = false;
1120 bool bFound = find_position( val, pos, key_comparator(), true );
1122 // scoped_node_ptr deletes the node tower if we create it before
1126 func( false, *node_traits::to_value_ptr(pos.pCur), val );
1127 m_Stat.onUpdateExist();
1128 return std::make_pair( true, false );
1133 return std::make_pair( false, false );
1137 build_node( pNode );
1138 nHeight = pNode->height();
1143 if ( !insert_at_position( val, pNode, pos, [&func]( value_type& item ) { func( true, item, item ); })) {
1144 m_Stat.onInsertRetry();
1148 increase_height( nHeight );
1151 m_Stat.onAddNode( nHeight );
1152 m_Stat.onUpdateNew();
1153 return std::make_pair( true, true );
1157 template <typename Func>
1158 CDS_DEPRECATED("ensure() is deprecated, use update()")
1159 std::pair<bool, bool> ensure( value_type& val, Func func )
1161 return update( val, func, true );
1165 /// Unlinks the item \p val from the set
1167 The function searches the item \p val in the set and unlink it from the set
1168 if it is found and is equal to \p val.
1170 Difference between \p erase() and \p %unlink() functions: \p %erase() finds <i>a key</i>
1171 and deletes the item found. \p %unlink() finds an item by key and deletes it
1172 only if \p val is an item of that set, i.e. the pointer to item found
1173 is equal to <tt> &val </tt>.
1175 The \p disposer specified in \p Traits class template parameter is called
1176 by garbage collector \p GC asynchronously.
1178 The function returns \p true if success and \p false otherwise.
1180 bool unlink( value_type& val )
1184 if ( !find_position( val, pos, key_comparator(), false ) ) {
1185 m_Stat.onUnlinkFailed();
1189 node_type * pDel = pos.pCur;
1190 assert( key_comparator()( *node_traits::to_value_ptr( pDel ), val ) == 0 );
1192 unsigned int nHeight = pDel->height();
1193 typename gc::Guard gDel;
1194 gDel.assign( node_traits::to_value_ptr(pDel) );
1196 if ( node_traits::to_value_ptr( pDel ) == &val && try_remove_at( pDel, pos, [](value_type const&) {} )) {
1198 m_Stat.onRemoveNode( nHeight );
1199 m_Stat.onUnlinkSuccess();
1203 m_Stat.onUnlinkFailed();
1207 /// Extracts the item from the set with specified \p key
1208 /** \anchor cds_intrusive_SkipListSet_hp_extract
1209 The function searches an item with key equal to \p key in the set,
1210 unlinks it from the set, and returns it as \p guarded_ptr object.
1211 If \p key is not found the function returns an empty guarded pointer.
1213 Note the compare functor should accept a parameter of type \p Q that can be not the same as \p value_type.
1215 The \p disposer specified in \p Traits class template parameter is called automatically
1216 by garbage collector \p GC specified in class' template parameters when returned \p guarded_ptr object
1217 will be destroyed or released.
1218 @note Each \p guarded_ptr object uses the GC's guard that can be limited resource.
1222 typedef cds::intrusive::SkipListSet< cds::gc::HP, foo, my_traits > skip_list;
1226 skip_list::guarded_ptr gp(theList.extract( 5 ));
1231 // Destructor of gp releases internal HP guard
1235 template <typename Q>
1236 guarded_ptr extract( Q const& key )
1239 extract_( gp.guard(), key, key_comparator() );
1243 /// Extracts the item from the set with comparing functor \p pred
1245 The function is an analog of \ref cds_intrusive_SkipListSet_hp_extract "extract(Q const&)"
1246 but \p pred predicate is used for key comparing.
1248 \p Less functor has the semantics like \p std::less but should take arguments of type \ref value_type and \p Q
1250 \p pred must imply the same element order as the comparator used for building the set.
1252 template <typename Q, typename Less>
1253 guarded_ptr extract_with( Q const& key, Less pred )
1257 extract_( gp.guard(), key, cds::opt::details::make_comparator_from_less<Less>() );
1261 /// Extracts an item with minimal key from the list
1263 The function searches an item with minimal key, unlinks it, and returns it as \p guarded_ptr object.
1264 If the skip-list is empty the function returns an empty guarded pointer.
1266 @note Due the concurrent nature of the list, the function extracts <i>nearly</i> minimum key.
1267 It means that the function gets leftmost item and tries to unlink it.
1268 During unlinking, a concurrent thread may insert an item with key less than leftmost item's key.
1269 So, the function returns the item with minimum key at the moment of list traversing.
1271 The \p disposer specified in \p Traits class template parameter is called
1272 by garbage collector \p GC automatically when returned \p guarded_ptr object
1273 will be destroyed or released.
1274 @note Each \p guarded_ptr object uses the GC's guard that can be limited resource.
1278 typedef cds::intrusive::SkipListSet< cds::gc::HP, foo, my_traits > skip_list;
1282 skip_list::guarded_ptr gp(theList.extract_min());
1287 // Destructor of gp releases internal HP guard
1291 guarded_ptr extract_min()
1294 extract_min_( gp.guard() );
1298 /// Extracts an item with maximal key from the list
1300 The function searches an item with maximal key, unlinks it, and returns the pointer to item
1301 as \p guarded_ptr object.
1302 If the skip-list is empty the function returns an empty \p guarded_ptr.
1304 @note Due the concurrent nature of the list, the function extracts <i>nearly</i> maximal key.
1305 It means that the function gets rightmost item and tries to unlink it.
1306 During unlinking, a concurrent thread may insert an item with key greater than rightmost item's key.
1307 So, the function returns the item with maximum key at the moment of list traversing.
1309 The \p disposer specified in \p Traits class template parameter is called
1310 by garbage collector \p GC asynchronously when returned \ref guarded_ptr object
1311 will be destroyed or released.
1312 @note Each \p guarded_ptr object uses the GC's guard that can be limited resource.
1316 typedef cds::intrusive::SkipListSet< cds::gc::HP, foo, my_traits > skip_list;
1320 skip_list::guarded_ptr gp( theList.extract_max( gp ));
1325 // Destructor of gp releases internal HP guard
1329 guarded_ptr extract_max()
1332 extract_max_( gp.guard() );
1336 /// Deletes the item from the set
1337 /** \anchor cds_intrusive_SkipListSet_hp_erase
1338 The function searches an item with key equal to \p key in the set,
1339 unlinks it from the set, and returns \p true.
1340 If the item with key equal to \p key is not found the function return \p false.
1342 Note the compare functor should accept a parameter of type \p Q that can be not the same as \p value_type.
1344 template <typename Q>
1345 bool erase( Q const& key )
1347 return erase_( key, key_comparator(), [](value_type const&) {} );
1350 /// Deletes the item from the set with comparing functor \p pred
1352 The function is an analog of \ref cds_intrusive_SkipListSet_hp_erase "erase(Q const&)"
1353 but \p pred predicate is used for key comparing.
1355 \p Less functor has the semantics like \p std::less but should take arguments of type \ref value_type and \p Q
1357 \p pred must imply the same element order as the comparator used for building the set.
1359 template <typename Q, typename Less>
1360 bool erase_with( Q const& key, Less pred )
1363 return erase_( key, cds::opt::details::make_comparator_from_less<Less>(), [](value_type const&) {} );
1366 /// Deletes the item from the set
1367 /** \anchor cds_intrusive_SkipListSet_hp_erase_func
1368 The function searches an item with key equal to \p key in the set,
1369 call \p f functor with item found, unlinks it from the set, and returns \p true.
1370 The \ref disposer specified in \p Traits class template parameter is called
1371 by garbage collector \p GC asynchronously.
1373 The \p Func interface is
1376 void operator()( value_type const& item );
1380 If the item with key equal to \p key is not found the function return \p false.
1382 Note the compare functor should accept a parameter of type \p Q that can be not the same as \p value_type.
1384 template <typename Q, typename Func>
1385 bool erase( Q const& key, Func f )
1387 return erase_( key, key_comparator(), f );
1390 /// Deletes the item from the set with comparing functor \p pred
1392 The function is an analog of \ref cds_intrusive_SkipListSet_hp_erase_func "erase(Q const&, Func)"
1393 but \p pred predicate is used for key comparing.
1395 \p Less functor has the semantics like \p std::less but should take arguments of type \ref value_type and \p Q
1397 \p pred must imply the same element order as the comparator used for building the set.
1399 template <typename Q, typename Less, typename Func>
1400 bool erase_with( Q const& key, Less pred, Func f )
1403 return erase_( key, cds::opt::details::make_comparator_from_less<Less>(), f );
1407 /** \anchor cds_intrusive_SkipListSet_hp_find_func
1408 The function searches the item with key equal to \p key and calls the functor \p f for item found.
1409 The interface of \p Func functor is:
1412 void operator()( value_type& item, Q& key );
1415 where \p item is the item found, \p key is the <tt>find</tt> function argument.
1417 The functor can change non-key fields of \p item. Note that the functor is only guarantee
1418 that \p item cannot be disposed during functor is executing.
1419 The functor does not serialize simultaneous access to the set \p item. If such access is
1420 possible you must provide your own synchronization on item level to exclude unsafe item modifications.
1422 Note the compare functor specified for class \p Traits template parameter
1423 should accept a parameter of type \p Q that can be not the same as \p value_type.
1425 The function returns \p true if \p key is found, \p false otherwise.
1427 template <typename Q, typename Func>
1428 bool find( Q& key, Func f )
1430 return find_with_( key, key_comparator(), f );
1433 template <typename Q, typename Func>
1434 bool find( Q const& key, Func f )
1436 return find_with_( key, key_comparator(), f );
1440 /// Finds the key \p key with \p pred predicate for comparing
1442 The function is an analog of \ref cds_intrusive_SkipListSet_hp_find_func "find(Q&, Func)"
1443 but \p pred is used for key compare.
1445 \p Less functor has the semantics like \p std::less but should take arguments of type \ref value_type and \p Q
1447 \p pred must imply the same element order as the comparator used for building the set.
1449 template <typename Q, typename Less, typename Func>
1450 bool find_with( Q& key, Less pred, Func f )
1453 return find_with_( key, cds::opt::details::make_comparator_from_less<Less>(), f );
1456 template <typename Q, typename Less, typename Func>
1457 bool find_with( Q const& key, Less pred, Func f )
1460 return find_with_( key, cds::opt::details::make_comparator_from_less<Less>(), f );
1464 /// Checks whether the set contains \p key
1466 The function searches the item with key equal to \p key
1467 and returns \p true if it is found, and \p false otherwise.
1469 template <typename Q>
1470 bool contains( Q const& key )
1472 return find_with_( key, key_comparator(), [](value_type& , Q const& ) {} );
1475 template <typename Q>
1476 CDS_DEPRECATED("deprecated, use contains()")
1477 bool find( Q const& key )
1479 return contains( key );
1483 /// Checks whether the set contains \p key using \p pred predicate for searching
1485 The function is similar to <tt>contains( key )</tt> but \p pred is used for key comparing.
1486 \p Less functor has the interface like \p std::less.
1487 \p Less must imply the same element order as the comparator used for building the set.
1489 template <typename Q, typename Less>
1490 bool contains( Q const& key, Less pred )
1493 return find_with_( key, cds::opt::details::make_comparator_from_less<Less>(), [](value_type& , Q const& ) {} );
1496 template <typename Q, typename Less>
1497 CDS_DEPRECATED("deprecated, use contains()")
1498 bool find_with( Q const& key, Less pred )
1500 return contains( key, pred );
1504 /// Finds \p key and return the item found
1505 /** \anchor cds_intrusive_SkipListSet_hp_get
1506 The function searches the item with key equal to \p key
1507 and returns the pointer to the item found as \p guarded_ptr.
1508 If \p key is not found the function returns an empt guarded pointer.
1510 The \p disposer specified in \p Traits class template parameter is called
1511 by garbage collector \p GC asynchronously when returned \ref guarded_ptr object
1512 will be destroyed or released.
1513 @note Each \p guarded_ptr object uses one GC's guard which can be limited resource.
1517 typedef cds::intrusive::SkipListSet< cds::gc::HP, foo, my_traits > skip_list;
1521 skip_list::guarded_ptr gp(theList.get( 5 ));
1526 // Destructor of guarded_ptr releases internal HP guard
1530 Note the compare functor specified for class \p Traits template parameter
1531 should accept a parameter of type \p Q that can be not the same as \p value_type.
1533 template <typename Q>
1534 guarded_ptr get( Q const& key )
1537 get_with_( gp.guard(), key, key_comparator() );
1541 /// Finds \p key and return the item found
1543 The function is an analog of \ref cds_intrusive_SkipListSet_hp_get "get( Q const&)"
1544 but \p pred is used for comparing the keys.
1546 \p Less functor has the semantics like \p std::less but should take arguments of type \ref value_type and \p Q
1548 \p pred must imply the same element order as the comparator used for building the set.
1550 template <typename Q, typename Less>
1551 guarded_ptr get_with( Q const& key, Less pred )
1555 get_with_( gp.guard(), key, cds::opt::details::make_comparator_from_less<Less>() );
1559 /// Returns item count in the set
1561 The value returned depends on item counter type provided by \p Traits template parameter.
1562 If it is \p atomicity::empty_item_counter this function always returns 0.
1563 Therefore, the function is not suitable for checking the set emptiness, use \p empty()
1568 return m_ItemCounter;
1571 /// Checks if the set is empty
1574 return m_Head.head()->next( 0 ).load( memory_model::memory_order_relaxed ) == nullptr;
1577 /// Clears the set (not atomic)
1579 The function unlink all items from the set.
1580 The function is not atomic, i.e., in multi-threaded environment with parallel insertions
1584 assert( set.empty() );
1586 the assertion could be raised.
1588 For each item the \ref disposer will be called after unlinking.
1593 while ( extract_min_( gp.guard() ));
1596 /// Returns maximum height of skip-list. The max height is a constant for each object and does not exceed 32.
1597 static CDS_CONSTEXPR unsigned int max_height() CDS_NOEXCEPT
1599 return c_nMaxHeight;
1602 /// Returns const reference to internal statistics
1603 stat const& statistics() const
1609 }} // namespace cds::intrusive
1612 #endif // #ifndef CDSLIB_INTRUSIVE_IMPL_SKIP_LIST_H