3 #ifndef __CDS_INTRUSIVE_SKIP_LIST_NOGC_H
4 #define __CDS_INTRUSIVE_SKIP_LIST_NOGC_H
8 #include <cds/gc/nogc.h>
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 {
17 template <typename Tag>
18 class node< cds::gc::nogc, Tag >
21 typedef cds::gc::nogc gc; ///< Garbage collector
22 typedef Tag tag; ///< tag
24 typedef atomics::atomic<node * > atomic_ptr;
25 typedef atomic_ptr tower_item_type;
28 atomic_ptr m_pNext; ///< Next item in bottom-list (list at level 0)
29 unsigned int m_nHeight; ///< Node height (size of m_arrNext array). For node at level 0 the height is 1.
30 atomic_ptr * m_arrNext; ///< Array of next items for levels 1 .. m_nHeight - 1. For node at level 0 \p m_arrNext is \p nullptr
33 /// Constructs a node of height 1 (a bottom-list node)
37 , m_arrNext( nullptr )
40 /// Constructs a node of height \p nHeight
41 void make_tower( unsigned int nHeight, atomic_ptr * nextTower )
43 assert( nHeight > 0 );
44 assert( (nHeight == 1 && nextTower == nullptr) // bottom-list node
45 || (nHeight > 1 && nextTower != nullptr) // node at level of more than 0
48 m_arrNext = nextTower;
52 atomic_ptr * release_tower()
54 atomic_ptr * pTower = m_arrNext;
60 atomic_ptr * get_tower() const
65 /// Access to element of next pointer array
66 atomic_ptr& next( unsigned int nLevel )
68 assert( nLevel < height() );
69 assert( nLevel == 0 || (nLevel > 0 && m_arrNext != nullptr) );
71 return nLevel ? m_arrNext[ nLevel - 1] : m_pNext;
74 /// Access to element of next pointer array (const version)
75 atomic_ptr const& next( unsigned int nLevel ) const
77 assert( nLevel < height() );
78 assert( nLevel == 0 || nLevel > 0 && m_arrNext != nullptr );
80 return nLevel ? m_arrNext[ nLevel - 1] : m_pNext;
83 /// Access to element of next pointer array (same as \ref next function)
84 atomic_ptr& operator[]( unsigned int nLevel )
86 return next( nLevel );
89 /// Access to element of next pointer array (same as \ref next function)
90 atomic_ptr const& operator[]( unsigned int nLevel ) const
92 return next( nLevel );
95 /// Height of the node
96 unsigned int height() const
101 /// Clears internal links
104 assert( m_arrNext == nullptr );
105 m_pNext.store( nullptr, atomics::memory_order_release );
108 bool is_cleared() const
110 return m_pNext.load( atomics::memory_order_relaxed ) == nullptr
111 && m_arrNext == nullptr
116 } // namespace skip_list
118 namespace skip_list { namespace details {
120 template <typename NodeTraits, typename BackOff, bool IsConst>
121 class iterator< cds::gc::nogc, NodeTraits, BackOff, IsConst>
124 typedef cds::gc::nogc gc;
125 typedef NodeTraits node_traits;
126 typedef BackOff back_off;
127 typedef typename node_traits::node_type node_type;
128 typedef typename node_traits::value_type value_type;
129 static bool const c_isConst = IsConst;
131 typedef typename std::conditional< c_isConst, value_type const &, value_type &>::type value_ref;
134 typedef typename node_type::atomic_ptr atomic_ptr;
137 public: // for internal use only!!!
138 iterator( node_type& refHead )
139 : m_pNode( refHead[0].load( atomics::memory_order_relaxed ) )
142 static iterator from_node( node_type * pNode )
149 node_type * node() const
159 iterator( iterator const& s)
160 : m_pNode( s.m_pNode )
163 value_type * operator ->() const
165 assert( m_pNode != nullptr );
166 assert( node_traits::to_value_ptr( m_pNode ) != nullptr );
168 return node_traits::to_value_ptr( m_pNode );
171 value_ref operator *() const
173 assert( m_pNode != nullptr );
174 assert( node_traits::to_value_ptr( m_pNode ) != nullptr );
176 return *node_traits::to_value_ptr( m_pNode );
180 iterator& operator ++()
183 m_pNode = m_pNode->next(0).load( atomics::memory_order_relaxed );
187 iterator& operator =(const iterator& src)
189 m_pNode = src.m_pNode;
193 template <typename Bkoff, bool C>
194 bool operator ==(iterator<gc, node_traits, Bkoff, C> const& i ) const
196 return m_pNode == i.node();
198 template <typename Bkoff, bool C>
199 bool operator !=(iterator<gc, node_traits, Bkoff, C> const& i ) const
201 return !( *this == i );
204 }} // namespace skip_list::details
207 /// Lock-free skip-list set (template specialization for gc::nogc)
208 /** @ingroup cds_intrusive_map
209 @anchor cds_intrusive_SkipListSet_nogc
211 This specialization is so-called append-only when no item
212 reclamation may be performed. The class does not support deleting of list item.
214 See \ref cds_intrusive_SkipListSet_hp "SkipListSet" for description of skip-list.
216 <b>Template arguments</b> :
217 - \p T - type to be stored in the set. The type must be based on \p skip_list::node (for \p skip_list::base_hook)
218 or it must have a member of type \p skip_list::node (for \p skip_list::member_hook).
219 - \p Traits - type traits, default is \p skip_list::traits.
220 It is possible to declare option-based list with \p cds::intrusive::skip_list::make_traits metafunction
221 istead of \p Traits template argument.
225 The class supports a forward iterator (\ref iterator and \ref const_iterator).
226 The iteration is ordered.
228 The iterator class supports the following minimalistic interface:
235 iterator( iterator const& s);
237 value_type * operator ->() const;
238 value_type& operator *() const;
241 iterator& operator ++();
244 iterator& operator = (const iterator& src);
246 bool operator ==(iterator const& i ) const;
247 bool operator !=(iterator const& i ) const;
250 Note, the iterator object returned by \ref end, \p cend member functions points to \p nullptr and should not be dereferenced.
254 You should incorporate \p skip_list::node into your struct \p T and provide
255 appropriate \p skip_list::traits::hook in your \p Traits template parameters. Usually, for \p Traits you
256 define a struct based on \p skip_list::traits.
258 Example for base hook:
260 #include <cds/intrusive/skip_list_nogc.h>
262 // Data stored in skip list
263 struct my_data: public cds::intrusive::skip_list::node< cds::gc::nogc >
272 // my_data compare functor
274 int operator()( const my_data& d1, const my_data& d2 )
276 return d1.strKey.compare( d2.strKey );
279 int operator()( const my_data& d, const std::string& s )
281 return d.strKey.compare(s);
284 int operator()( const std::string& s, const my_data& d )
286 return s.compare( d.strKey );
292 struct my_traits: public cds::intrusive::skip_list::traits
294 typedef cds::intrusive::skip_list::base_hook< cds::opt::gc< cds::gc::nogc > > hook;
295 typedef my_data_cmp compare;
298 // Declare skip-list set type
299 typedef cds::intrusive::SkipListSet< cds::gc::nogc, my_data, my_traits > traits_based_set;
302 Equivalent option-based code:
304 // GC-related specialization
305 #include <cds/intrusive/skip_list_nogc.h>
314 // Declare option-based skip-list set
315 typedef cds::intrusive::SkipListSet< cds::gc::nogc
317 , typename cds::intrusive::skip_list::make_traits<
318 cds::intrusive::opt::hook< cds::intrusive::skip_list::base_hook< cds::opt::gc< cds::gc::nogc > > >
319 ,cds::intrusive::opt::compare< my_data_cmp >
326 #ifdef CDS_DOXYGEN_INVOKED
327 ,typename Traits = skip_list::traits
332 class SkipListSet< cds::gc::nogc, T, Traits >
335 typedef cds::gc::nogc gc; ///< No garbage collector is used
336 typedef T value_type; ///< type of value stored in the skip-list
337 typedef Traits traits; ///< Traits template parameter
339 typedef typename traits::hook hook; ///< hook type
340 typedef typename hook::node_type node_type; ///< node type
342 # ifdef CDS_DOXYGEN_INVOKED
343 typedef implementation_defined key_comparator; ///< key comparison functor based on \p Traits::compare and \p Traits::less
345 typedef typename opt::details::make_comparator< value_type, traits >::type key_comparator;
347 typedef typename get_node_traits< value_type, node_type, hook>::type node_traits; ///< node traits
349 typedef typename traits::item_counter item_counter; ///< Item counting policy
350 typedef typename traits::memory_model memory_model; ///< Memory ordering. See \p cds::opt::memory_model option
351 typedef typename traits::random_level_generator random_level_generator ; ///< random level generator
352 typedef typename traits::allocator allocator_type; ///< allocator for maintaining array of next pointers of the node
353 typedef typename traits::back_off back_off; ///< Back-off strategy
354 typedef typename traits::stat stat; ///< internal statistics type
355 typedef typename traits::disposer disposer; ///< disposer
357 /// Max node height. The actual node height should be in range <tt>[0 .. c_nMaxHeight)</tt>
359 The max height is specified by \ref skip_list::random_level_generator "random level generator" constant \p m_nUpperBound
360 but it should be no more than 32 (\p skip_list::c_nHeightLimit).
362 static unsigned int const c_nMaxHeight = std::conditional<
363 (random_level_generator::c_nUpperBound <= skip_list::c_nHeightLimit),
364 std::integral_constant< unsigned int, random_level_generator::c_nUpperBound >,
365 std::integral_constant< unsigned int, skip_list::c_nHeightLimit >
369 static unsigned int const c_nMinHeight = 3;
373 typedef typename node_type::atomic_ptr atomic_node_ptr; ///< Atomic node pointer
377 typedef skip_list::details::intrusive_node_builder< node_type, atomic_node_ptr, allocator_type > intrusive_node_builder;
379 typedef typename std::conditional<
380 std::is_same< typename traits::internal_node_builder, cds::opt::none >::value
381 ,intrusive_node_builder
382 ,typename traits::internal_node_builder
383 >::type node_builder;
385 typedef std::unique_ptr< node_type, typename node_builder::node_disposer > scoped_node_ptr;
388 node_type * pPrev[ c_nMaxHeight ];
389 node_type * pSucc[ c_nMaxHeight ];
394 class head_node: public node_type
396 typename node_type::atomic_ptr m_Tower[c_nMaxHeight];
399 head_node( unsigned int nHeight )
401 for ( size_t i = 0; i < sizeof(m_Tower) / sizeof(m_Tower[0]); ++i )
402 m_Tower[i].store( nullptr, atomics::memory_order_relaxed );
404 node_type::make_tower( nHeight, m_Tower );
407 node_type * head() const
409 return const_cast<node_type *>( static_cast<node_type const *>(this));
414 for (unsigned int i = 0; i < sizeof(m_Tower) / sizeof(m_Tower[0]); ++i )
415 m_Tower[i].store( nullptr, atomics::memory_order_relaxed );
416 node_type::m_pNext.store( nullptr, atomics::memory_order_relaxed );
422 head_node m_Head; ///< head tower (max height)
424 item_counter m_ItemCounter; ///< item counter
425 random_level_generator m_RandomLevelGen; ///< random level generator instance
426 atomics::atomic<unsigned int> m_nHeight; ///< estimated high level
427 mutable stat m_Stat; ///< internal statistics
431 unsigned int random_level()
433 // Random generator produces a number from range [0..31]
434 // We need a number from range [1..32]
435 return m_RandomLevelGen() + 1;
438 template <typename Q>
439 node_type * build_node( Q v )
441 return node_builder::make_tower( v, m_RandomLevelGen );
444 static void dispose_node( node_type * pNode )
446 assert( pNode != nullptr );
447 typename node_builder::node_disposer()( pNode );
448 disposer()( node_traits::to_value_ptr( pNode ));
451 template <typename Q, typename Compare >
452 bool find_position( Q const& val, position& pos, Compare cmp, bool bStopIfFound, bool bStrictSearch ) const
456 node_type * pCur = nullptr;
460 unsigned int nHeight = c_nMaxHeight;
462 if ( !bStrictSearch )
463 nHeight = m_nHeight.load( memory_model::memory_order_relaxed );
464 pPred = m_Head.head();
466 for ( int nLevel = (int) nHeight - 1; nLevel >= 0; --nLevel ) {
468 pCur = pPred->next( nLevel ).load( memory_model::memory_order_relaxed );
471 // end of the list at level nLevel - goto next level
475 pSucc = pCur->next( nLevel ).load( memory_model::memory_order_relaxed );
477 if ( pPred->next( nLevel ).load( memory_model::memory_order_acquire ) != pCur
478 || pCur->next( nLevel ).load( memory_model::memory_order_acquire ) != pSucc )
483 nCmp = cmp( *node_traits::to_value_ptr( pCur ), val );
486 else if ( nCmp == 0 && bStopIfFound )
492 pos.pPrev[ nLevel ] = pPred;
493 pos.pSucc[ nLevel ] = pCur;
501 return pCur && nCmp == 0;
504 template <typename Func>
505 bool insert_at_position( value_type& val, node_type * pNode, position& pos, Func f )
507 unsigned int nHeight = pNode->height();
509 for ( unsigned int nLevel = 1; nLevel < nHeight; ++nLevel )
510 pNode->next( nLevel ).store( nullptr, memory_model::memory_order_relaxed );
513 node_type * p = pos.pSucc[0];
514 pNode->next( 0 ).store( pos.pSucc[ 0 ], memory_model::memory_order_release );
515 if ( !pos.pPrev[0]->next(0).compare_exchange_strong( p, pNode, memory_model::memory_order_release, memory_model::memory_order_relaxed ) ) {
521 for ( unsigned int nLevel = 1; nLevel < nHeight; ++nLevel ) {
522 node_type * p = nullptr;
524 node_type * q = pos.pSucc[ nLevel ];
526 if ( pNode->next( nLevel ).compare_exchange_strong( p, q, memory_model::memory_order_release, memory_model::memory_order_relaxed )) {
528 if ( pos.pPrev[nLevel]->next(nLevel).compare_exchange_strong( q, pNode, memory_model::memory_order_release, memory_model::memory_order_relaxed ) )
532 // Renew insert position
533 find_position( val, pos, key_comparator(), false, true );
539 template <typename Q, typename Compare, typename Func>
540 node_type * find_with_( Q& val, Compare cmp, Func f ) const
543 if ( find_position( val, pos, cmp, true, false )) {
544 assert( cmp( *node_traits::to_value_ptr( pos.pCur ), val ) == 0 );
546 f( *node_traits::to_value_ptr( pos.pCur ), val );
548 m_Stat.onFindFastSuccess();
552 m_Stat.onFindFastFailed();
557 void increase_height( unsigned int nHeight )
559 unsigned int nCur = m_nHeight.load( memory_model::memory_order_relaxed );
560 while ( nCur < nHeight && !m_nHeight.compare_exchange_weak( nCur, nHeight, memory_model::memory_order_acquire, atomics::memory_order_relaxed ) );
565 /// Default constructor
567 The constructor checks whether the count of guards is enough
568 for skip-list and may raise an exception if not.
571 : m_Head( c_nMaxHeight )
572 , m_nHeight( c_nMinHeight )
574 static_assert( (std::is_same< gc, typename node_type::gc >::value), "GC and node_type::gc must be the same type" );
576 // Barrier for head node
577 atomics::atomic_thread_fence( memory_model::memory_order_release );
580 /// Clears and destructs the skip-list
588 typedef skip_list::details::iterator< gc, node_traits, back_off, false > iterator;
590 /// Const iterator type
591 typedef skip_list::details::iterator< gc, node_traits, back_off, true > const_iterator;
593 /// Returns a forward iterator addressing the first element in a set
596 return iterator( *m_Head.head() );
599 /// Returns a forward const iterator addressing the first element in a set
600 const_iterator begin() const
602 return const_iterator( *m_Head.head() );
604 /// Returns a forward const iterator addressing the first element in a set
605 const_iterator cbegin() const
607 return const_iterator( *m_Head.head() );
610 /// Returns a forward iterator that addresses the location succeeding the last element in a set.
616 /// Returns a forward const iterator that addresses the location succeeding the last element in a set.
617 const_iterator end() const
619 return const_iterator();
621 /// Returns a forward const iterator that addresses the location succeeding the last element in a set.
622 const_iterator cend() const
624 return const_iterator();
629 iterator nonconst_end() const
638 The function inserts \p val in the set if it does not contain
639 an item with key equal to \p val.
641 Returns \p true if \p val is placed into the set, \p false otherwise.
643 bool insert( value_type& val )
645 node_type * pNode = node_traits::to_node_ptr( val );
646 scoped_node_ptr scp( pNode );
647 unsigned int nHeight = pNode->height();
648 bool bTowerOk = nHeight > 1 && pNode->get_tower() != nullptr;
649 bool bTowerMade = false;
654 bool bFound = find_position( val, pos, key_comparator(), true, true );
656 // scoped_node_ptr deletes the node tower if we create it
660 m_Stat.onInsertFailed();
666 nHeight = pNode->height();
671 if ( !insert_at_position( val, pNode, pos, []( value_type& ) {} )) {
672 m_Stat.onInsertRetry();
676 increase_height( nHeight );
678 m_Stat.onAddNode( nHeight );
679 m_Stat.onInsertSuccess();
685 /// Ensures that the \p val exists in the set
687 The operation performs inserting or changing data with lock-free manner.
689 If the item \p val is not found in the set, then \p val is inserted into the set.
690 Otherwise, the functor \p func is called with item found.
691 The functor signature is:
693 void func( bool bNew, value_type& item, value_type& val );
696 - \p bNew - \p true if the item has been inserted, \p false otherwise
697 - \p item - item of the set
698 - \p val - argument \p val passed into the \p ensure function
699 If new item has been inserted (i.e. \p bNew is \p true) then \p item and \p val arguments
700 refer to the same thing.
702 The functor can change non-key fields of the \p item; however, \p func must guarantee
703 that during changing no any other modifications could be made on this item by concurrent threads.
705 Returns std::pair<bool, bool> where \p first is \p true if operation is successfull,
706 \p second is \p true if new item has been added or \p false if the item with \p key
707 already is in the set.
709 @warning See \ref cds_intrusive_item_creating "insert item troubleshooting"
711 template <typename Func>
712 std::pair<bool, bool> ensure( value_type& val, Func func )
714 node_type * pNode = node_traits::to_node_ptr( val );
715 scoped_node_ptr scp( pNode );
716 unsigned int nHeight = pNode->height();
717 bool bTowerOk = nHeight > 1 && pNode->get_tower() != nullptr;
718 bool bTowerMade = false;
723 bool bFound = find_position( val, pos, key_comparator(), true, true );
725 // scoped_node_ptr deletes the node tower if we create it before
729 func( false, *node_traits::to_value_ptr(pos.pCur), val );
730 m_Stat.onEnsureExist();
731 return std::make_pair( true, false );
736 nHeight = pNode->height();
741 if ( !insert_at_position( val, pNode, pos, [&func]( value_type& item ) { func( true, item, item ); })) {
742 m_Stat.onInsertRetry();
746 increase_height( nHeight );
749 m_Stat.onAddNode( nHeight );
750 m_Stat.onEnsureNew();
751 return std::make_pair( true, true );
756 /** \anchor cds_intrusive_SkipListSet_nogc_find_func
757 The function searches the item with key equal to \p key and calls the functor \p f for item found.
758 The interface of \p Func functor is:
761 void operator()( value_type& item, Q& key );
764 where \p item is the item found, \p key is the <tt>find</tt> function argument.
766 The functor can change non-key fields of \p item. Note that the functor is only guarantee
767 that \p item cannot be disposed during functor is executing.
768 The functor does not serialize simultaneous access to the set \p item. If such access is
769 possible you must provide your own synchronization schema on item level to exclude unsafe item modifications.
771 The \p key argument is non-const since it can be used as \p f functor destination i.e., the functor
772 can modify both arguments.
774 Note the hash functor specified for class \p Traits template parameter
775 should accept a parameter of type \p Q that can be not the same as \p value_type.
777 The function returns \p true if \p key is found, \p false otherwise.
779 template <typename Q, typename Func>
780 bool find( Q& key, Func f ) const
782 return find_with_( key, key_comparator(), f ) != nullptr;
785 /// Finds the key \p key using \p pred predicate for comparing
787 The function is an analog of \ref cds_intrusive_SkipListSet_nogc_find_func "find(Q&, Func)"
788 but \p pred predicate is used for key compare.
789 \p Less has the interface like \p std::less.
790 \p pred must imply the same element order as the comparator used for building the set.
792 template <typename Q, typename Less, typename Func>
793 bool find_with( Q& key, Less pred, Func f ) const
795 return find_with_( key, cds::opt::details::make_comparator_from_less<Less>(), f ) != nullptr;
799 /** \anchor cds_intrusive_SkipListSet_nogc_find_val
800 The function searches the item with key equal to \p key
801 and returns \p true if it is found, and \p false otherwise.
803 Note the hash functor specified for class \p Traits template parameter
804 should accept a parameter of type \p Q that can be not the same as \p value_type.
806 template <typename Q>
807 value_type * find( Q const& key ) const
809 node_type * pNode = find_with_( key, key_comparator(), [](value_type& , Q const& ) {} );
811 return node_traits::to_value_ptr( pNode );
815 /// Finds \p key using \p pred predicate for comparing
817 The function is an analog of \ref cds_intrusive_SkipListSet_nogc_find_val "find(Q const&)"
818 but \p pred predicate is used for key compare.
819 \p Less has the interface like \p std::less.
820 \p pred must imply the same element order as the comparator used for building the set.
822 template <typename Q, typename Less>
823 value_type * find_with( Q const& key, Less pred ) const
825 node_type * pNode = find_with_( key, cds::opt::details::make_comparator_from_less<Less>(), [](value_type& , Q const& ) {} );
827 return node_traits::to_value_ptr( pNode );
831 /// Gets minimum key from the set
833 If the set is empty the function returns \p nullptr
835 value_type * get_min() const
837 return node_traits::to_value_ptr( m_Head.head()->next( 0 ));
840 /// Gets maximum key from the set
842 The function returns \p nullptr if the set is empty
844 value_type * get_max() const
848 unsigned int nHeight = m_nHeight.load( memory_model::memory_order_relaxed );
849 pPred = m_Head.head();
851 for ( int nLevel = (int) nHeight - 1; nLevel >= 0; --nLevel ) {
853 node_type * pCur = pPred->next( nLevel ).load( memory_model::memory_order_relaxed );
856 // end of the list at level nLevel - goto next level
862 return pPred && pPred != m_Head.head() ? node_traits::to_value_ptr( pPred ) : nullptr;
865 /// Clears the set (non-atomic)
867 The function is not atomic.
868 Finding and/or inserting is prohibited while clearing.
869 Otherwise an unpredictable result may be encountered.
870 Thus, \p clear() may be used only for debugging purposes.
874 node_type * pNode = m_Head.head()->next(0).load( memory_model::memory_order_relaxed );
876 m_ItemCounter.reset();
877 m_nHeight.store( c_nMinHeight, memory_model::memory_order_release );
880 node_type * pNext = pNode->next(0).load( memory_model::memory_order_relaxed );
881 dispose_node( pNode );
886 /// Returns item count in the set
888 The value returned depends on item counter type provided by \p Traits template parameter.
889 For \p atomicity::empty_item_counter the function always returns 0.
890 The function is not suitable for checking the set emptiness, use \p empty().
894 return m_ItemCounter;
897 /// Checks if the set is empty
900 return m_Head.head()->next( 0 ).load( memory_model::memory_order_relaxed ) == nullptr;
903 /// Returns maximum height of skip-list. The max height is a constant for each object and does not exceed 32.
904 static CDS_CONSTEXPR unsigned int max_height() CDS_NOEXCEPT
909 /// Returns const reference to internal statistics
910 stat const& statistics() const
916 }} // namespace cds::intrusive
919 #endif // #ifndef __CDS_INTRUSIVE_SKIP_LIST_IMPL_H