2 This file is a part of libcds - Concurrent Data Structures library
4 (C) Copyright Maxim Khizhinsky (libcds.dev@gmail.com) 2006-2017
6 Source code repo: http://github.com/khizmax/libcds/
7 Download: http://sourceforge.net/projects/libcds/files/
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions are met:
12 * Redistributions of source code must retain the above copyright notice, this
13 list of conditions and the following disclaimer.
15 * Redistributions in binary form must reproduce the above copyright notice,
16 this list of conditions and the following disclaimer in the documentation
17 and/or other materials provided with the distribution.
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #ifndef CDSLIB_INTRUSIVE_SPLIT_LIST_H
32 #define CDSLIB_INTRUSIVE_SPLIT_LIST_H
35 #include <cds/intrusive/details/split_list_base.h>
36 #include <cds/details/type_padding.h>
38 namespace cds { namespace intrusive {
40 /// Split-ordered list
41 /** @ingroup cds_intrusive_map
42 \anchor cds_intrusive_SplitListSet_hp
44 Hash table implementation based on split-ordered list algorithm discovered by Ori Shalev and Nir Shavit, see
45 - [2003] Ori Shalev, Nir Shavit "Split-Ordered Lists - Lock-free Resizable Hash Tables"
46 - [2008] Nir Shavit "The Art of Multiprocessor Programming"
48 The split-ordered list is a lock-free implementation of an extensible unbounded hash table. It uses original
49 recursive split-ordering algorithm discovered by Ori Shalev and Nir Shavit that allows to split buckets
50 without item moving on resizing.
52 \anchor cds_SplitList_algo_desc
53 <b>Short description</b>
54 [from [2003] Ori Shalev, Nir Shavit "Split-Ordered Lists - Lock-free Resizable Hash Tables"]
56 The algorithm keeps all the items in one lock-free linked list, and gradually assigns the bucket pointers to
57 the places in the list where a sublist of 'correct' items can be found. A bucket is initialized upon first
58 access by assigning it to a new 'dummy' node (dashed contour) in the list, preceding all items that should be
59 in that bucket. A newly created bucket splits an older bucket's chain, reducing the access cost to its items. The
60 table uses a modulo 2**i hash (there are known techniques for 'pre-hashing' before a modulo 2**i hash
61 to overcome possible binary correlations among values). The table starts at size 2 and repeatedly doubles in size.
63 Unlike moving an item, the operation of directing a bucket pointer can be done
64 in a single CAS operation, and since items are not moved, they are never 'lost'.
65 However, to make this approach work, one must be able to keep the items in the
66 list sorted in such a way that any bucket's sublist can be 'split' by directing a new
67 bucket pointer within it. This operation must be recursively repeatable, as every
68 split bucket may be split again and again as the hash table grows. To achieve this
69 goal the authors introduced recursive split-ordering, a new ordering on keys that keeps items
70 in a given bucket adjacent in the list throughout the repeated splitting process.
72 Magically, yet perhaps not surprisingly, recursive split-ordering is achieved by
73 simple binary reversal: reversing the bits of the hash key so that the new key's
74 most significant bits (MSB) are those that were originally its least significant.
75 The split-order keys of regular nodes are exactly the bit-reverse image of the original
76 keys after turning on their MSB. For example, items 9 and 13 are in the <tt>1 mod
77 4</tt> bucket, which can be recursively split in two by inserting a new node between
80 To insert (respectively delete or search for) an item in the hash table, hash its
81 key to the appropriate bucket using recursive split-ordering, follow the pointer to
82 the appropriate location in the sorted items list, and traverse the list until the key's
83 proper location in the split-ordering (respectively until the key or a key indicating
84 the item is not in the list is found). Because of the combinatorial structure induced
85 by the split-ordering, this will require traversal of no more than an expected constant number of items.
87 The design is modular: to implement the ordered items list, you can use one of several
88 non-blocking list-based set algorithms: MichaelList, LazyList.
92 Template parameters are:
93 - \p GC - Garbage collector. Note the \p GC must be the same as the \p GC used for \p OrderedList
94 - \p OrderedList - ordered list implementation used as a bucket for hash set, for example, \p MichaelList, \p LazyList.
95 The intrusive ordered list implementation specifies the type \p T stored in the split-list set, the comparison
96 functor for the type \p T and other features specific for the ordered list.
97 - \p Traits - split-list traits, default is \p split_list::traits.
98 Instead of defining \p Traits struct you can use option-based syntax provided by \p split_list::make_traits metafunction.
100 There are several specialization of the split-list class for different \p GC:
101 - for \ref cds_urcu_gc "RCU type" include <tt><cds/intrusive/split_list_rcu.h></tt> - see
102 \ref cds_intrusive_SplitListSet_rcu "RCU-based split-list"
103 - for cds::gc::nogc include <tt><cds/intrusive/split_list_nogc.h></tt> - see
104 \ref cds_intrusive_SplitListSet_nogc "persistent SplitListSet".
106 \anchor cds_SplitList_hash_functor
109 Some member functions of split-ordered list accept the key parameter of type \p Q which differs from \p value_type.
110 It is expected that type \p Q contains full key of \p value_type, and for equal keys of type \p Q and \p value_type
111 the hash values of these keys must be equal too.
112 The hash functor \p Traits::hash should accept parameters of both type:
116 std::string key_ ; // key field
122 size_t operator()( const std::string& s ) const
124 return std::hash( s );
127 size_t operator()( const Foo& f ) const
129 return (*this)( f.key_ );
136 Split-list based on \p IterableList differs from split-list based on \p MichaelList or \p LazyList
137 because \p %IterableList stores data "as is" - it cannot use any hook.
139 Suppose, your split-list contains values of type \p Foo.
140 For \p %MichaelList and \p %LazyList, \p Foo declaration should be based on ordered-list node:
143 struct Foo: public cds::intrusive::split_list::node< cds::intrusive::michael_list::node< cds::gc::HP > >
145 // ... field declarations
150 struct Foo: public cds::intrusive::split_list::node< cds::intrusive::lazy_list::node< cds::gc::HP > >
152 // ... field declarations
156 For \p %IterableList, \p Foo should be based on \p void:
158 struct Foo: public cds::intrusive::split_list::node<void>
160 // ... field declarations
164 Everything else is the same.
165 Consider split-list based on \p MichaelList.
167 First, you should choose ordered list type to use in your split-list set:
169 // For gc::HP-based MichaelList implementation
170 #include <cds/intrusive/michael_list_hp.h>
172 // cds::intrusive::SplitListSet declaration
173 #include <cds/intrusive/split_list.h>
176 // Note you should declare your struct based on cds::intrusive::split_list::node
177 // which is a wrapper for ordered-list node struct.
178 // In our case, the node type for HP-based MichaelList is cds::intrusive::michael_list::node< cds::gc::HP >
179 struct Foo: public cds::intrusive::split_list::node< cds::intrusive::michael_list::node< cds::gc::HP > >
181 std::string key_ ; // key field
182 unsigned val_ ; // value field
183 // ... other value fields
186 // Declare comparator for the item
189 int operator()( const Foo& f1, const Foo& f2 ) const
191 return f1.key_.compare( f2.key_ );
195 // Declare base ordered-list type for split-list
196 typedef cds::intrusive::MichaelList< cds::gc::HP, Foo,
197 typename cds::intrusive::michael_list::make_traits<
199 cds::intrusive::opt::hook< cds::intrusive::michael_list::base_hook< cds::opt::gc< cds::gc::HP > > >
200 // item comparator option
201 ,cds::opt::compare< FooCmp >
206 Second, you should declare split-list set container:
209 // Declare hash functor
210 // Note, the hash functor accepts parameter type Foo and std::string
212 size_t operator()( const Foo& f ) const
214 return cds::opt::v::hash<std::string>()( f.key_ );
216 size_t operator()( const std::string& s ) const
218 return cds::opt::v::hash<std::string>()( s );
222 // Split-list set typedef
223 typedef cds::intrusive::SplitListSet<
226 ,typename cds::intrusive::split_list::make_traits<
227 cds::opt::hash< FooHash >
232 Now, you can use \p Foo_set in your application.
238 fooSet.insert( *foo );
246 # ifdef CDS_DOXYGEN_INVOKED
247 class Traits = split_list::traits
255 typedef GC gc; ///< Garbage collector
256 typedef Traits traits; ///< Set traits
260 typedef split_list::details::rebind_list_traits<OrderedList, traits> ordered_list_adapter;
264 # ifdef CDS_DOXYGEN_INVOKED
265 typedef OrderedList ordered_list; ///< type of ordered list used as a base for split-list
267 typedef typename ordered_list_adapter::result ordered_list;
269 typedef typename ordered_list::value_type value_type; ///< type of value stored in the split-list
270 typedef typename ordered_list::key_comparator key_comparator; ///< key comparison functor
271 typedef typename ordered_list::disposer disposer; ///< Node disposer functor
273 /// Hash functor for \p %value_type and all its derivatives you use
274 typedef typename cds::opt::v::hash_selector< typename traits::hash >::type hash;
276 typedef typename traits::item_counter item_counter; ///< Item counter type
277 typedef typename traits::back_off back_off; ///< back-off strategy for spinning
278 typedef typename traits::memory_model memory_model; ///< Memory ordering. See \p cds::opt::memory_model option
279 typedef typename traits::stat stat; ///< Internal statistics, see \p spit_list::stat
280 typedef typename ordered_list::guarded_ptr guarded_ptr; ///< Guarded pointer
282 /// Count of hazard pointer required
283 static CDS_CONSTEXPR const size_t c_nHazardPtrCount = ordered_list::c_nHazardPtrCount + 4; // +4 - for iterators
287 typedef split_list::node<typename ordered_list_adapter::ordered_list_node_type> node_type; ///< split-list node type
288 typedef typename ordered_list_adapter::node_traits node_traits;
290 /// Bucket table implementation
291 typedef typename split_list::details::bucket_table_selector<
292 traits::dynamic_bucket_table
294 , typename ordered_list_adapter::aux_node
295 , opt::allocator< typename traits::allocator >
296 , opt::memory_model< memory_model >
297 , opt::free_list< typename traits::free_list >
298 >::type bucket_table;
300 typedef typename bucket_table::aux_node_type aux_node_type; ///< auxiliary node type
305 /// Ordered list wrapper to access protected members
306 class ordered_list_wrapper: public ordered_list
308 typedef ordered_list base_class;
309 typedef typename base_class::auxiliary_head bucket_head_type;
312 bool insert_at( aux_node_type* pHead, value_type& val )
314 assert( pHead != nullptr );
315 bucket_head_type h(pHead);
316 return base_class::insert_at( h, val );
319 template <typename Func>
320 bool insert_at( aux_node_type * pHead, value_type& val, Func f )
322 assert( pHead != nullptr );
323 bucket_head_type h(pHead);
324 return base_class::insert_at( h, val, f );
327 template <typename Func>
328 std::pair<bool, bool> update_at( aux_node_type * pHead, value_type& val, Func func, bool bAllowInsert )
330 assert( pHead != nullptr );
331 bucket_head_type h(pHead);
332 return base_class::update_at( h, val, func, bAllowInsert );
335 template <typename Q>
336 typename std::enable_if<
337 std::is_same< Q, value_type>::value && is_iterable_list< ordered_list >::value,
338 std::pair<bool, bool>
340 upsert_at( aux_node_type * pHead, Q& val, bool bAllowInsert )
342 assert( pHead != nullptr );
343 bucket_head_type h( pHead );
344 return base_class::upsert_at( h, val, bAllowInsert );
347 bool unlink_at( aux_node_type * pHead, value_type& val )
349 assert( pHead != nullptr );
350 bucket_head_type h(pHead);
351 return base_class::unlink_at( h, val );
354 template <typename Iterator>
355 typename std::enable_if<
356 std::is_same< Iterator, typename ordered_list::iterator>::value && is_iterable_list< ordered_list >::value,
359 erase_at( Iterator iter )
361 return base_class::erase_at( iter );
364 template <typename Q, typename Compare, typename Func>
365 bool erase_at( aux_node_type * pHead, split_list::details::search_value_type<Q> const& val, Compare cmp, Func f )
367 assert( pHead != nullptr );
368 bucket_head_type h(pHead);
369 return base_class::erase_at( h, val, cmp, f );
372 template <typename Q, typename Compare>
373 bool erase_at( aux_node_type * pHead, split_list::details::search_value_type<Q> const& val, Compare cmp )
375 assert( pHead != nullptr );
376 bucket_head_type h(pHead);
377 return base_class::erase_at( h, val, cmp );
380 template <typename Q, typename Compare>
381 guarded_ptr extract_at( aux_node_type * pHead, split_list::details::search_value_type<Q> const& val, Compare cmp )
383 assert( pHead != nullptr );
384 bucket_head_type h(pHead);
385 return base_class::extract_at( h, val, cmp );
388 template <typename Q, typename Compare, typename Func>
389 bool find_at( aux_node_type * pHead, split_list::details::search_value_type<Q>& val, Compare cmp, Func f )
391 assert( pHead != nullptr );
392 bucket_head_type h(pHead);
393 return base_class::find_at( h, val, cmp, f );
396 template <typename Q, typename Compare>
397 bool find_at( aux_node_type * pHead, split_list::details::search_value_type<Q> const& val, Compare cmp )
399 assert( pHead != nullptr );
400 bucket_head_type h(pHead);
401 return base_class::find_at( h, val, cmp );
404 template <typename Q, typename Compare>
405 typename std::enable_if<
406 std::is_same<Q, Q>::value && is_iterable_list< ordered_list >::value,
407 typename base_class::iterator
409 find_iterator_at( aux_node_type * pHead, split_list::details::search_value_type<Q> const& val, Compare cmp )
411 assert( pHead != nullptr );
412 bucket_head_type h( pHead );
413 return base_class::find_iterator_at( h, val, cmp );
416 template <typename Q, typename Compare>
417 guarded_ptr get_at( aux_node_type * pHead, split_list::details::search_value_type<Q> const& val, Compare cmp )
419 assert( pHead != nullptr );
420 bucket_head_type h(pHead);
421 return base_class::get_at( h, val, cmp );
424 bool insert_aux_node( aux_node_type * pNode )
426 return base_class::insert_aux_node( pNode );
428 bool insert_aux_node( aux_node_type * pHead, aux_node_type * pNode )
430 bucket_head_type h(pHead);
431 return base_class::insert_aux_node( h, pNode );
434 template <typename Predicate>
435 void destroy( Predicate pred )
437 base_class::destroy( pred );
444 template <bool IsConst>
446 : public split_list::details::iterator_type<node_traits, ordered_list, IsConst>
448 typedef split_list::details::iterator_type<node_traits, ordered_list, IsConst> iterator_base_class;
449 typedef typename iterator_base_class::list_iterator list_iterator;
451 friend class SplitListSet;
455 : iterator_base_class()
458 iterator_type( iterator_type const& src )
459 : iterator_base_class( src )
462 // This ctor should be protected...
463 iterator_type( list_iterator itCur, list_iterator itEnd )
464 : iterator_base_class( itCur, itEnd )
470 ///@name Forward iterators
474 The forward iterator is based on \p OrderedList forward iterator and has some features:
475 - it has no post-increment operator
476 - it iterates items in unordered fashion
477 - iterator cannot be moved across thread boundary because it may contain GC's guard that is thread-private GC data.
479 Iterator thread safety depends on type of \p OrderedList:
480 - for \p MichaelList and \p LazyList: iterator guarantees safety even if you delete the item that iterator points to
481 because that item is guarded by hazard pointer.
482 However, in case of concurrent deleting operations it is no guarantee that you iterate all item in the set.
483 Moreover, a crash is possible when you try to iterate the next element that has been deleted by concurrent thread.
484 Use this iterator on the concurrent container for debugging purpose only.
485 - for \p IterableList: iterator is thread-safe. You may use it freely in concurrent environment.
487 typedef iterator_type<false> iterator;
489 /// Const forward iterator
491 For iterator's features and requirements see \ref iterator
493 typedef iterator_type<true> const_iterator;
495 /// Returns a forward iterator addressing the first element in a split-list
497 For empty list \code begin() == end() \endcode
501 return iterator( m_List.begin(), m_List.end());
504 /// Returns an iterator that addresses the location succeeding the last element in a split-list
506 Do not use the value returned by <tt>end</tt> function to access any item.
508 The returned value can be used only to control reaching the end of the split-list.
509 For empty list \code begin() == end() \endcode
513 return iterator( m_List.end(), m_List.end());
516 /// Returns a forward const iterator addressing the first element in a split-list
517 const_iterator begin() const
521 /// Returns a forward const iterator addressing the first element in a split-list
522 const_iterator cbegin() const
524 return const_iterator( m_List.cbegin(), m_List.cend());
527 /// Returns an const iterator that addresses the location succeeding the last element in a split-list
528 const_iterator end() const
532 /// Returns an const iterator that addresses the location succeeding the last element in a split-list
533 const_iterator cend() const
535 return const_iterator( m_List.cend(), m_List.cend());
540 /// Initialize split-ordered list of default capacity
542 The default capacity is defined in bucket table constructor.
543 See \p split_list::expandable_bucket_table, \p split_list::static_bucket_table
544 which selects by \p split_list::dynamic_bucket_table option.
547 : m_nBucketCountLog2(1)
548 , m_nMaxItemCount( max_item_count(2, m_Buckets.load_factor()))
553 /// Initialize split-ordered list
555 size_t nItemCount ///< estimate average of item count
556 , size_t nLoadFactor = 1 ///< load factor - average item count per bucket. Small integer up to 8, default is 1.
558 : m_Buckets( nItemCount, nLoadFactor )
559 , m_nBucketCountLog2(1)
560 , m_nMaxItemCount( max_item_count(2, m_Buckets.load_factor()))
565 /// Destroys split-list set
568 // list contains aux node that cannot be retired
569 // all aux nodes will be destroyed by bucket table dtor
571 []( node_type * pNode ) -> bool {
572 return !pNode->is_dummy();
581 The function inserts \p val in the set if it does not contain
582 an item with key equal to \p val.
584 Returns \p true if \p val is placed into the set, \p false otherwise.
586 bool insert( value_type& val )
588 size_t nHash = hash_value( val );
589 aux_node_type * pHead = get_bucket( nHash );
590 assert( pHead != nullptr );
592 node_traits::to_node_ptr( val )->m_nHash = split_list::regular_hash( nHash );
594 if ( m_List.insert_at( pHead, val )) {
596 m_Stat.onInsertSuccess();
599 m_Stat.onInsertFailed();
605 This function is intended for derived non-intrusive containers.
607 The function allows to split creating of new item into two part:
608 - create item with key only
609 - insert new item into the set
610 - if inserting is success, calls \p f functor to initialize value-field of \p val.
612 The functor signature is:
614 void func( value_type& val );
616 where \p val is the item inserted.
617 The user-defined functor is called only if the inserting is success.
619 @warning For \ref cds_intrusive_MichaelList_hp "MichaelList" as the bucket see \ref cds_intrusive_item_creating "insert item troubleshooting".
620 \ref cds_intrusive_LazyList_hp "LazyList" provides exclusive access to inserted item and does not require any node-level
623 template <typename Func>
624 bool insert( value_type& val, Func f )
626 size_t nHash = hash_value( val );
627 aux_node_type * pHead = get_bucket( nHash );
628 assert( pHead != nullptr );
630 node_traits::to_node_ptr( val )->m_nHash = split_list::regular_hash( nHash );
632 if ( m_List.insert_at( pHead, val, f )) {
634 m_Stat.onInsertSuccess();
637 m_Stat.onInsertFailed();
643 The operation performs inserting or changing data with lock-free manner.
645 If the item \p val is not found in the set, then \p val is inserted
646 iff \p bAllowInsert is \p true.
647 Otherwise, the functor \p func is called with item found.
649 The functor signature depends of the type of \p OrderedList:
651 <b>for \p MichaelList, \p LazyList</b>
654 void operator()( bool bNew, value_type& item, value_type& val );
658 - \p bNew - \p true if the item has been inserted, \p false otherwise
659 - \p item - item of the set
660 - \p val - argument \p val passed into the \p %update() function
661 If new item has been inserted (i.e. \p bNew is \p true) then \p item and \p val arguments
662 refers to the same thing.
664 The functor may change non-key fields of the \p item.
665 @warning For \ref cds_intrusive_MichaelList_hp "MichaelList" as the bucket see \ref cds_intrusive_item_creating "insert item troubleshooting".
666 \ref cds_intrusive_LazyList_hp "LazyList" provides exclusive access to inserted item and does not require any node-level
669 <b>for \p IterableList</b>
671 void func( value_type& val, value_type * old );
674 - \p val - argument \p val passed into the \p %update() function
675 - \p old - old value that will be retired. If new item has been inserted then \p old is \p nullptr.
677 Returns std::pair<bool, bool> where \p first is \p true if operation is successful,
678 \p second is \p true if new item has been added or \p false if the item with \p val
679 already is in the list.
681 template <typename Func>
682 std::pair<bool, bool> update( value_type& val, Func func, bool bAllowInsert = true )
684 size_t nHash = hash_value( val );
685 aux_node_type * pHead = get_bucket( nHash );
686 assert( pHead != nullptr );
688 node_traits::to_node_ptr( val )->m_nHash = split_list::regular_hash( nHash );
690 std::pair<bool, bool> bRet = m_List.update_at( pHead, val, func, bAllowInsert );
691 if ( bRet.first && bRet.second ) {
693 m_Stat.onUpdateNew();
696 m_Stat.onUpdateExist();
700 template <typename Func>
701 CDS_DEPRECATED("ensure() is deprecated, use update()")
702 std::pair<bool, bool> ensure( value_type& val, Func func )
704 return update( val, func, true );
708 /// Inserts or updates the node (only for \p IterableList)
710 The operation performs inserting or changing data with lock-free manner.
712 If the item \p val is not found in the set, then \p val is inserted iff \p bAllowInsert is \p true.
713 Otherwise, the current element is changed to \p val, the old element will be retired later
714 by call \p Traits::disposer.
716 Returns std::pair<bool, bool> where \p first is \p true if operation is successful,
717 \p second is \p true if \p val has been added or \p false if the item with that key
720 #ifdef CDS_DOXYGEN_INVOKED
721 std::pair<bool, bool> upsert( value_type& val, bool bAllowInsert = true )
723 template <typename Q>
724 typename std::enable_if<
725 std::is_same< Q, value_type>::value && is_iterable_list< ordered_list >::value,
726 std::pair<bool, bool>
728 upsert( Q& val, bool bAllowInsert = true )
731 size_t nHash = hash_value( val );
732 aux_node_type * pHead = get_bucket( nHash );
733 assert( pHead != nullptr );
735 node_traits::to_node_ptr( val )->m_nHash = split_list::regular_hash( nHash );
737 std::pair<bool, bool> bRet = m_List.upsert_at( pHead, val, bAllowInsert );
738 if ( bRet.first && bRet.second ) {
740 m_Stat.onUpdateNew();
743 m_Stat.onUpdateExist();
747 /// Unlinks the item \p val from the set
749 The function searches the item \p val in the set and unlinks it from the set
750 if it is found and is equal to \p val.
752 Difference between \ref erase and \p unlink functions: \p erase finds <i>a key</i>
753 and deletes the item found. \p unlink finds an item by key and deletes it
754 only if \p val is an item of that set, i.e. the pointer to item found
755 is equal to <tt> &val </tt>.
757 The function returns \p true if success and \p false otherwise.
759 bool unlink( value_type& val )
761 size_t nHash = hash_value( val );
762 aux_node_type * pHead = get_bucket( nHash );
763 assert( pHead != nullptr );
765 if ( m_List.unlink_at( pHead, val )) {
767 m_Stat.onEraseSuccess();
770 m_Stat.onEraseFailed();
774 /// Deletes the item from the set
775 /** \anchor cds_intrusive_SplitListSet_hp_erase
776 The function searches an item with key equal to \p key in the set,
777 unlinks it from the set, and returns \p true.
778 If the item with key equal to \p key is not found the function return \p false.
780 Difference between \ref erase and \p unlink functions: \p erase finds <i>a key</i>
781 and deletes the item found. \p unlink finds an item by key and deletes it
782 only if \p key is an item of that set, i.e. the pointer to item found
783 is equal to <tt> &key </tt>.
785 Note the hash functor should accept a parameter of type \p Q that can be not the same as \p value_type.
787 template <typename Q>
788 bool erase( Q const& key )
790 return erase_( key, key_comparator());
793 /// Deletes the item from the set with comparing functor \p pred
796 The function is an analog of \ref cds_intrusive_SplitListSet_hp_erase "erase(Q const&)"
797 but \p pred predicate is used for key comparing.
798 \p Less has the interface like \p std::less.
799 \p pred must imply the same element order as the comparator used for building the set.
801 template <typename Q, typename Less>
802 bool erase_with( const Q& key, Less pred )
805 return erase_( key, typename ordered_list_adapter::template make_compare_from_less<Less>());
808 /// Deletes the item from the set
809 /** \anchor cds_intrusive_SplitListSet_hp_erase_func
810 The function searches an item with key equal to \p key in the set,
811 call \p f functor with item found, unlinks it from the set, and returns \p true.
812 The \ref disposer specified by \p OrderedList class template parameter is called
813 by garbage collector \p GC asynchronously.
815 The \p Func interface is
818 void operator()( value_type const& item );
822 If the item with key equal to \p key is not found the function return \p false.
824 Note the hash functor should accept a parameter of type \p Q that can be not the same as \p value_type.
826 template <typename Q, typename Func>
827 bool erase( Q const& key, Func f )
829 return erase_( key, key_comparator(), f );
832 /// Deletes the item from the set with comparing functor \p pred
834 The function is an analog of \ref cds_intrusive_SplitListSet_hp_erase_func "erase(Q const&, Func)"
835 but \p pred predicate is used for key comparing.
836 \p Less has the interface like \p std::less.
837 \p pred must imply the same element order as the comparator used for building the set.
839 template <typename Q, typename Less, typename Func>
840 bool erase_with( Q const& key, Less pred, Func f )
843 return erase_( key, typename ordered_list_adapter::template make_compare_from_less<Less>(), f );
846 /// Deletes the item pointed by iterator \p iter (only for \p IterableList based set)
848 Returns \p true if the operation is successful, \p false otherwise.
849 The function can return \p false if the node the iterator points to has already been deleted
852 The function does not invalidate the iterator, it remains valid and can be used for further traversing.
854 @note \p %erase_at() is supported only for \p %SplitListSet based on \p IterableList.
856 #ifdef CDS_DOXYGEN_INVOKED
857 bool erase_at( iterator const& iter )
859 template <typename Iterator>
860 typename std::enable_if< std::is_same<Iterator, iterator>::value && is_iterable_list< ordered_list >::value, bool >::type
861 erase_at( Iterator const& iter )
864 assert( iter != end() );
866 if ( m_List.erase_at( iter.underlying_iterator())) {
868 m_Stat.onEraseSuccess();
874 /// Extracts the item with specified \p key
875 /** \anchor cds_intrusive_SplitListSet_hp_extract
876 The function searches an item with key equal to \p key,
877 unlinks it from the set, and returns it as \p guarded_ptr.
878 If \p key is not found the function returns an empty guarded pointer.
880 Note the compare functor should accept a parameter of type \p Q that may be not the same as \p value_type.
882 The \p disposer specified in \p OrderedList class' template parameter is called automatically
883 by garbage collector \p GC when returned \p guarded_ptr object will be destroyed or released.
884 @note Each \p guarded_ptr object uses the GC's guard that can be limited resource.
888 typedef cds::intrusive::SplitListSet< your_template_args > splitlist_set;
889 splitlist_set theSet;
892 splitlist_set::guarded_ptr gp( theSet.extract( 5 ));
897 // Destructor of gp releases internal HP guard
901 template <typename Q>
902 guarded_ptr extract( Q const& key )
904 return extract_( key );
907 /// Extracts the item using compare functor \p pred
909 The function is an analog of \ref cds_intrusive_SplitListSet_hp_extract "extract(Q const&)"
910 but \p pred predicate is used for key comparing.
912 \p Less functor has the semantics like \p std::less but should take arguments of type \ref value_type and \p Q
914 \p pred must imply the same element order as the comparator used for building the set.
916 template <typename Q, typename Less>
917 guarded_ptr extract_with( Q const& key, Less pred )
919 return extract_with_( key, pred );
922 /// Finds the key \p key
923 /** \anchor cds_intrusive_SplitListSet_hp_find_func
924 The function searches the item with key equal to \p key and calls the functor \p f for item found.
925 The interface of \p Func functor is:
928 void operator()( value_type& item, Q& key );
931 where \p item is the item found, \p key is the <tt>find</tt> function argument.
933 The functor can change non-key fields of \p item. Note that the functor is only guarantee
934 that \p item cannot be disposed during functor is executing.
935 The functor does not serialize simultaneous access to the set \p item. If such access is
936 possible you must provide your own synchronization schema on item level to exclude unsafe item modifications.
938 Note the hash functor specified for class \p Traits template parameter
939 should accept a parameter of type \p Q that can be not the same as \p value_type.
941 The function returns \p true if \p key is found, \p false otherwise.
943 template <typename Q, typename Func>
944 bool find( Q& key, Func f )
946 return find_( key, key_comparator(), f );
949 template <typename Q, typename Func>
950 bool find( Q const& key, Func f )
952 return find_( key, key_comparator(), f );
956 /// Finds \p key and returns iterator pointed to the item found (only for \p IterableList)
958 If \p key is not found the function returns \p end().
960 @note This function is supported only for the set based on \p IterableList
962 template <typename Q>
963 #ifdef CDS_DOXYGEN_INVOKED
966 typename std::enable_if< std::is_same<Q, Q>::value && is_iterable_list< ordered_list >::value, iterator >::type
970 return find_iterator_( key, key_comparator());
973 template <typename Q>
974 typename std::enable_if< std::is_same<Q, Q>::value && is_iterable_list< ordered_list >::value, iterator >::type
977 return find_iterator_( key, key_comparator());
982 /// Finds the key \p key with \p pred predicate for comparing
984 The function is an analog of \ref cds_intrusive_SplitListSet_hp_find_func "find(Q&, Func)"
985 but \p cmp is used for key compare.
986 \p Less has the interface like \p std::less.
987 \p cmp must imply the same element order as the comparator used for building the set.
989 template <typename Q, typename Less, typename Func>
990 bool find_with( Q& key, Less pred, Func f )
993 return find_( key, typename ordered_list_adapter::template make_compare_from_less<Less>(), f );
996 template <typename Q, typename Less, typename Func>
997 bool find_with( Q const& key, Less pred, Func f )
1000 return find_( key, typename ordered_list_adapter::template make_compare_from_less<Less>(), f );
1004 /// Finds \p key using \p pred predicate and returns iterator pointed to the item found (only for \p IterableList)
1006 The function is an analog of \p find(Q&) but \p pred is used for key comparing.
1007 \p Less functor has the interface like \p std::less.
1008 \p pred must imply the same element order as the comparator used for building the set.
1010 If \p key is not found the function returns \p end().
1012 @note This function is supported only for the set based on \p IterableList
1014 template <typename Q, typename Less>
1015 #ifdef CDS_DOXYGEN_INVOKED
1018 typename std::enable_if< std::is_same<Q, Q>::value && is_iterable_list< ordered_list >::value, iterator >::type
1020 find_with( Q& key, Less pred )
1023 return find_iterator_( key, typename ordered_list_adapter::template make_compare_from_less<Less>());
1026 template <typename Q, typename Less>
1027 typename std::enable_if< std::is_same<Q, Q>::value && is_iterable_list< ordered_list >::value, iterator >::type
1028 find_with( Q const& key, Less pred )
1031 return find_iterator_( key, typename ordered_list_adapter::template make_compare_from_less<Less>());
1036 /// Checks whether the set contains \p key
1038 The function searches the item with key equal to \p key
1039 and returns \p true if it is found, and \p false otherwise.
1041 Note the hash functor specified for class \p Traits template parameter
1042 should accept a parameter of type \p Q that can be not the same as \p value_type.
1043 Otherwise, you may use \p contains( Q const&, Less pred ) functions with explicit predicate for key comparing.
1045 template <typename Q>
1046 bool contains( Q const& key )
1048 return find_( key, key_comparator());
1051 /// Checks whether the set contains \p key using \p pred predicate for searching
1053 The function is an analog of <tt>contains( key )</tt> but \p pred is used for key comparing.
1054 \p Less functor has the interface like \p std::less.
1055 \p Less must imply the same element order as the comparator used for building the set.
1057 template <typename Q, typename Less>
1058 bool contains( Q const& key, Less pred )
1061 return find_( key, typename ordered_list_adapter::template make_compare_from_less<Less>());
1064 /// Finds the key \p key and return the item found
1065 /** \anchor cds_intrusive_SplitListSet_hp_get
1066 The function searches the item with key equal to \p key
1067 and returns the item found as \p guarded_ptr.
1068 If \p key is not found the function returns an empty guarded pointer.
1070 The \p disposer specified in \p OrderedList class' template parameter is called
1071 by garbage collector \p GC automatically when returned \p guarded_ptr object
1072 will be destroyed or released.
1073 @note Each \p guarded_ptr object uses one GC's guard which can be limited resource.
1077 typedef cds::intrusive::SplitListSet< your_template_params > splitlist_set;
1078 splitlist_set theSet;
1081 splitlist_set::guarded_ptr gp = theSet.get( 5 );
1086 // Destructor of guarded_ptr releases internal HP guard
1090 Note the compare functor specified for \p OrderedList template parameter
1091 should accept a parameter of type \p Q that can be not the same as \p value_type.
1093 template <typename Q>
1094 guarded_ptr get( Q const& key )
1099 /// Finds the key \p key and return the item found
1101 The function is an analog of \ref cds_intrusive_SplitListSet_hp_get "get( Q const&)"
1102 but \p pred is used for comparing the keys.
1104 \p Less functor has the semantics like \p std::less but should take arguments of type \ref value_type and \p Q
1106 \p pred must imply the same element order as the comparator used for building the set.
1108 template <typename Q, typename Less>
1109 guarded_ptr get_with( Q const& key, Less pred )
1111 return get_with_( key, pred );
1114 /// Returns item count in the set
1117 return m_ItemCounter;
1120 /// Checks if the set is empty
1122 Emptiness is checked by item counting: if item count is zero then the set is empty.
1123 Thus, the correct item counting feature is an important part of split-list set implementation.
1130 /// Clears the set (non-atomic)
1132 The function unlink all items from the set.
1133 The function is not atomic. After call the split-list can be non-empty.
1135 For each item the \p disposer is called after unlinking.
1139 iterator it = begin();
1140 while ( it != end()) {
1148 /// Returns internal statistics
1149 stat const& statistics() const
1154 /// Returns internal statistics for \p OrderedList
1155 typename OrderedList::stat const& list_statistics() const
1157 return m_List.statistics();
1162 aux_node_type * alloc_aux_node( size_t nHash )
1164 m_Stat.onHeadNodeAllocated();
1165 aux_node_type* p = m_Buckets.alloc_aux_node();
1171 void free_aux_node( aux_node_type * p )
1173 m_Buckets.free_aux_node( p );
1174 m_Stat.onHeadNodeFreed();
1177 /// Calculates hash value of \p key
1178 template <typename Q>
1179 size_t hash_value( Q const& key ) const
1181 return m_HashFunctor( key );
1184 size_t bucket_no( size_t nHash ) const
1186 return nHash & ((1 << m_nBucketCountLog2.load( memory_model::memory_order_relaxed )) - 1);
1189 static size_t parent_bucket( size_t nBucket )
1191 assert( nBucket > 0 );
1192 return nBucket & ~(1 << bitop::MSBnz( nBucket ));
1195 aux_node_type * init_bucket( size_t const nBucket )
1197 assert( nBucket > 0 );
1198 size_t nParent = parent_bucket( nBucket );
1200 aux_node_type * pParentBucket = m_Buckets.bucket( nParent );
1201 if ( pParentBucket == nullptr ) {
1202 pParentBucket = init_bucket( nParent );
1203 m_Stat.onRecursiveInitBucket();
1206 assert( pParentBucket != nullptr );
1208 // Allocate an aux node for new bucket
1209 aux_node_type * pBucket = m_Buckets.bucket( nBucket );
1212 for ( ;; pBucket = m_Buckets.bucket( nBucket )) {
1216 pBucket = alloc_aux_node( split_list::dummy_hash( nBucket ));
1218 if ( m_List.insert_aux_node( pParentBucket, pBucket )) {
1219 m_Buckets.bucket( nBucket, pBucket );
1220 m_Stat.onNewBucket();
1224 // Another thread set the bucket. Wait while it done
1225 free_aux_node( pBucket );
1226 m_Stat.onBucketInitContenton();
1230 // There are no free buckets. It means that the bucket table is full
1231 // Wait while another thread set the bucket or a free bucket will be available
1232 m_Stat.onBucketsExhausted();
1236 // Another thread set the bucket. Wait while it done
1237 for ( pBucket = m_Buckets.bucket( nBucket ); pBucket == nullptr; pBucket = m_Buckets.bucket( nBucket )) {
1239 m_Stat.onBusyWaitBucketInit();
1245 aux_node_type * get_bucket( size_t nHash )
1247 size_t nBucket = bucket_no( nHash );
1249 aux_node_type * pHead = m_Buckets.bucket( nBucket );
1250 if ( pHead == nullptr )
1251 pHead = init_bucket( nBucket );
1253 assert( pHead->is_dummy());
1260 // GC and OrderedList::gc must be the same
1261 static_assert(std::is_same<gc, typename ordered_list::gc>::value, "GC and OrderedList::gc must be the same");
1263 // atomicity::empty_item_counter is not allowed as a item counter
1264 static_assert(!std::is_same<item_counter, cds::atomicity::empty_item_counter>::value,
1265 "cds::atomicity::empty_item_counter is not allowed as a item counter");
1267 // Initialize bucket 0
1268 aux_node_type * pNode = alloc_aux_node( 0 /*split_list::dummy_hash(0)*/ );
1269 assert( pNode != nullptr );
1271 // insert_aux_node cannot return false for empty list
1272 CDS_VERIFY( m_List.insert_aux_node( pNode ));
1274 m_Buckets.bucket( 0, pNode );
1277 static size_t max_item_count( size_t nBucketCount, size_t nLoadFactor )
1279 return nBucketCount * nLoadFactor;
1282 void inc_item_count()
1284 size_t nMaxCount = m_nMaxItemCount.load( memory_model::memory_order_relaxed );
1285 if ( ++m_ItemCounter <= nMaxCount )
1288 size_t sz = m_nBucketCountLog2.load( memory_model::memory_order_relaxed );
1289 const size_t nBucketCount = static_cast<size_t>(1) << sz;
1290 if ( nBucketCount < m_Buckets.capacity()) {
1291 // we may grow the bucket table
1292 const size_t nLoadFactor = m_Buckets.load_factor();
1293 if ( nMaxCount < max_item_count( nBucketCount, nLoadFactor ))
1294 return; // someone already have updated m_nBucketCountLog2, so stop here
1296 m_nMaxItemCount.compare_exchange_strong( nMaxCount, max_item_count( nBucketCount << 1, nLoadFactor ),
1297 memory_model::memory_order_relaxed, atomics::memory_order_relaxed );
1298 m_nBucketCountLog2.compare_exchange_strong( sz, sz + 1, memory_model::memory_order_relaxed, atomics::memory_order_relaxed );
1301 m_nMaxItemCount.store( std::numeric_limits<size_t>::max(), memory_model::memory_order_relaxed );
1304 template <typename Q, typename Compare, typename Func>
1305 bool find_( Q& val, Compare cmp, Func f )
1307 size_t nHash = hash_value( val );
1308 split_list::details::search_value_type<Q> sv( val, split_list::regular_hash( nHash ));
1309 aux_node_type * pHead = get_bucket( nHash );
1310 assert( pHead != nullptr );
1312 return m_Stat.onFind(
1313 m_List.find_at( pHead, sv, cmp,
1314 [&f]( value_type& item, split_list::details::search_value_type<Q>& val ) { f( item, val.val ); } )
1318 template <typename Q, typename Compare>
1319 bool find_( Q const& val, Compare cmp )
1321 size_t nHash = hash_value( val );
1322 split_list::details::search_value_type<Q const> sv( val, split_list::regular_hash( nHash ));
1323 aux_node_type * pHead = get_bucket( nHash );
1324 assert( pHead != nullptr );
1326 return m_Stat.onFind( m_List.find_at( pHead, sv, cmp ));
1329 template <typename Q, typename Compare>
1330 iterator find_iterator_( Q const& val, Compare cmp )
1332 size_t nHash = hash_value( val );
1333 split_list::details::search_value_type<Q const> sv( val, split_list::regular_hash( nHash ));
1334 aux_node_type * pHead = get_bucket( nHash );
1335 assert( pHead != nullptr );
1337 return iterator( m_List.find_iterator_at( pHead, sv, cmp ), m_List.end());
1340 template <typename Q, typename Compare>
1341 guarded_ptr get_( Q const& val, Compare cmp )
1343 size_t nHash = hash_value( val );
1344 split_list::details::search_value_type<Q const> sv( val, split_list::regular_hash( nHash ));
1345 aux_node_type * pHead = get_bucket( nHash );
1346 assert( pHead != nullptr );
1348 guarded_ptr gp = m_List.get_at( pHead, sv, cmp );
1349 m_Stat.onFind( !gp.empty());
1353 template <typename Q>
1354 guarded_ptr get_( Q const& key )
1356 return get_( key, key_comparator());
1359 template <typename Q, typename Less>
1360 guarded_ptr get_with_( Q const& key, Less )
1362 return get_( key, typename ordered_list_adapter::template make_compare_from_less<Less>());
1365 template <typename Q, typename Compare, typename Func>
1366 bool erase_( Q const& val, Compare cmp, Func f )
1368 size_t nHash = hash_value( val );
1369 split_list::details::search_value_type<Q const> sv( val, split_list::regular_hash( nHash ));
1370 aux_node_type * pHead = get_bucket( nHash );
1371 assert( pHead != nullptr );
1373 if ( m_List.erase_at( pHead, sv, cmp, f )) {
1375 m_Stat.onEraseSuccess();
1378 m_Stat.onEraseFailed();
1382 template <typename Q, typename Compare>
1383 bool erase_( Q const& val, Compare cmp )
1385 size_t nHash = hash_value( val );
1386 split_list::details::search_value_type<Q const> sv( val, split_list::regular_hash( nHash ));
1387 aux_node_type * pHead = get_bucket( nHash );
1388 assert( pHead != nullptr );
1390 if ( m_List.erase_at( pHead, sv, cmp )) {
1392 m_Stat.onEraseSuccess();
1395 m_Stat.onEraseFailed();
1399 template <typename Q, typename Compare>
1400 guarded_ptr extract_( Q const& val, Compare cmp )
1402 size_t nHash = hash_value( val );
1403 split_list::details::search_value_type<Q const> sv( val, split_list::regular_hash( nHash ));
1404 aux_node_type * pHead = get_bucket( nHash );
1405 assert( pHead != nullptr );
1407 guarded_ptr gp = m_List.extract_at( pHead, sv, cmp );
1410 m_Stat.onExtractSuccess();
1413 m_Stat.onExtractFailed();
1417 template <typename Q>
1418 guarded_ptr extract_( Q const& key )
1420 return extract_( key, key_comparator());
1423 template <typename Q, typename Less>
1424 guarded_ptr extract_with_( Q const& key, Less )
1426 return extract_( key, typename ordered_list_adapter::template make_compare_from_less<Less>());
1432 static unsigned const c_padding = cds::opt::actual_padding< traits::padding >::value;
1434 typedef typename cds::details::type_padding< bucket_table, c_padding >::type padded_bucket_table;
1435 padded_bucket_table m_Buckets; ///< bucket table
1437 typedef typename cds::details::type_padding< ordered_list_wrapper, c_padding >::type padded_ordered_list;
1438 padded_ordered_list m_List; ///< Ordered list containing split-list items
1440 atomics::atomic<size_t> m_nBucketCountLog2; ///< log2( current bucket count )
1441 atomics::atomic<size_t> m_nMaxItemCount; ///< number of items container can hold, before we have to resize
1442 item_counter m_ItemCounter; ///< Item counter
1443 hash m_HashFunctor; ///< Hash functor
1444 stat m_Stat; ///< Internal statistics
1448 }} // namespace cds::intrusive
1450 #endif // #ifndef CDSLIB_INTRUSIVE_SPLIT_LIST_H