X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=cds%2Fcontainer%2Fbronson_avltree_map_rcu.h;h=51a94da30ac511d073941dfd6aa6b98ea46f3e69;hb=e0488e8f808c943d3a479fd7abd12549066aaf98;hp=413f19bc064f5f8ff5c89da311641f0f387d3e17;hpb=450b0235199e8124d92d0d23b0950013e399c0bc;p=libcds.git diff --git a/cds/container/bronson_avltree_map_rcu.h b/cds/container/bronson_avltree_map_rcu.h index 413f19bc..51a94da3 100644 --- a/cds/container/bronson_avltree_map_rcu.h +++ b/cds/container/bronson_avltree_map_rcu.h @@ -1,34 +1,37 @@ //$$CDS-header$$ -#ifndef __CDS_CONTAINER_BRONSON_AVLTREE_MAP_RCU_H -#define __CDS_CONTAINER_BRONSON_AVLTREE_MAP_RCU_H +#ifndef CDSLIB_CONTAINER_BRONSON_AVLTREE_MAP_RCU_H +#define CDSLIB_CONTAINER_BRONSON_AVLTREE_MAP_RCU_H +#include #include namespace cds { namespace container { - namespace bronson_avltree { + namespace bronson_avltree { //@cond namespace details { - - template - struct value_node : public bronson_avltree::node< Key, T, Lock > - { - T m_data; // placeholder for data - }; - - template - struct pointer_oriented_traits: public Traits + template < class RCU, typename Key, typename T, typename Traits> + struct make_map { - struct disposer { - template - void operator()( T * p ) - { - std::allocator().destroy( p ); - } + typedef Key key_type; + typedef T mapped_type; + typedef Traits original_traits; + + typedef cds::details::Allocator< mapped_type, typename original_traits::allocator > cxx_allocator; + + struct traits : public original_traits + { + struct disposer { + void operator()( mapped_type * p ) const + { + cxx_allocator().Delete( p ); + } + }; }; - typedef value_node node_type; + // Metafunction result + typedef BronsonAVLTreeMap< RCU, Key, mapped_type *, traits > type; }; } // namespace details //@endcond @@ -41,8 +44,25 @@ namespace cds { namespace container { Source: - [2010] N.Bronson, J.Casper, H.Chafi, K.Olukotun "A Practical Concurrent Binary Search Tree" - - bla-bla-bla + - Java implementation + + This is a concurrent AVL tree algorithm that uses hand-over-hand optimistic validation, + a concurrency control mechanism for searching and navigating a binary search tree. + This mechanism minimizes spurious retries when concurrent structural changes cannot + affect the correctness of the search or navigation result. + The algorithm is based on partially external trees, a simple scheme that simplifies deletions + by leaving a routing node in the tree when deleting a node that has two children, + then opportunistically unlinking routing nodes during rebalancing. As in external trees, + which store values only in leaf nodes, deletions can be performed locally while holding + a fixed number of locks. Partially external trees, however, require far fewer routing nodes + than an external tree for most sequences of insertions and deletions. + The algorithm uses optimistic concurrency control, but carefully manage the + tree in such a way that all atomic regions have fixed read and write sets + that are known ahead of time. This allows to reduce practical overheads by embedding + the concurrency control directly. To perform tree operations using only fixed sized + atomic regions the algo uses the following mechanisms: search operations overlap atomic blocks as + in the hand-over-hand locking technique; mutations perform rebalancing separately; + and deletions occasionally leave a routing node in the tree. Template arguments: - \p RCU - one of \ref cds_urcu_gc "RCU type" @@ -52,10 +72,11 @@ namespace cds { namespace container { It is possible to declare option-based tree with \p bronson_avltree::make_traits metafunction instead of \p Traits template argument. + There is \ref cds_container_BronsonAVLTreeMap_rcu_ptr "a specialization" for "key -> value pointer" map. + @note Before including you should include appropriate RCU header file, see \ref cds_urcu_gc "RCU type" for list of existing RCU class and corresponding header files. */ - template < typename RCU, typename Key, @@ -67,10 +88,15 @@ namespace cds { namespace container { #endif > class BronsonAVLTreeMap< cds::urcu::gc, Key, T, Traits > - : private BronsonAVLTreeMap< cds::urcu::gc, Key, T*, bronson_avltree::details::pointer_oriented_traits> +#ifdef CDS_DOXYGEN_INVOKED + : private BronsonAVLTreeMap< cds::urcu::gc, Key, T*, Traits > +#else + : private bronson_avltree::details::make_map< cds::urcu::gc, Key, T, Traits >::type +#endif { //@cond - typedef BronsonAVLTreeMap< cds::urcu::gc, Key, T*, bronson_avltree::details::pointer_oriented_traits> base_class; + typedef bronson_avltree::details::make_map< cds::urcu::gc, Key, T, Traits > maker; + typedef typename maker::type base_class; //@endcond public: @@ -82,16 +108,31 @@ namespace cds { namespace container { typedef typename base_class::key_comparator key_comparator; ///< key compare functor based on \p Traits::compare and \p Traits::less typedef typename traits::item_counter item_counter; ///< Item counting policy typedef typename traits::memory_model memory_model; ///< Memory ordering, see \p cds::opt::memory_model option - typedef typename traits::allocator allocator_type; ///< allocator for maintaining internal node + typedef typename traits::allocator allocator_type; ///< allocator for value + typedef typename traits::node_allocator node_allocator_type;///< allocator for maintaining internal nodes typedef typename traits::stat stat; ///< internal statistics typedef typename traits::rcu_check_deadlock rcu_check_deadlock; ///< Deadlock checking policy typedef typename traits::back_off back_off; ///< Back-off strategy - typedef typename traits::lock_type lock_type; ///< Node lock type + typedef typename traits::sync_monitor sync_monitor; ///< @ref cds_sync_monitor "Synchronization monitor" type for node-level locking + + /// Enabled or disabled @ref bronson_avltree::relaxed_insert "relaxed insertion" + static bool const c_bRelaxedInsert = traits::relaxed_insert; + + /// Group of \p extract_xxx functions does not require external locking + static CDS_CONSTEXPR const bool c_bExtractLockExternal = base_class::c_bExtractLockExternal; + + typedef typename base_class::rcu_lock rcu_lock; ///< RCU scoped lock + + /// Returned pointer to \p mapped_type of extracted node + typedef typename base_class::exempt_ptr exempt_ptr; protected: //@cond - typedef typename base_class::alloc_node_type node_type; - typedef base_class::node_scoped_lock node_scoped_lock; + typedef typename base_class::node_type node_type; + typedef typename base_class::node_scoped_lock node_scoped_lock; + typedef typename maker::cxx_allocator cxx_allocator; + + typedef typename base_class::update_flags update_flags; //@endcond public: @@ -103,7 +144,7 @@ namespace cds { namespace container { ~BronsonAVLTreeMap() {} - /// Inserts new node with key and default value + /// Inserts new node with \p key and default value /** The function creates a node with \p key and default value, and then inserts the node created into the map. @@ -118,7 +159,15 @@ namespace cds { namespace container { template bool insert( K const& key ) { - //TODO + return base_class::do_update(key, key_comparator(), + []( node_type * pNode ) -> mapped_type* + { + assert( pNode->m_pValue.load( memory_model::memory_order_relaxed ) == nullptr ); + CDS_UNUSED( pNode ); + return cxx_allocator().New(); + }, + update_flags::allow_insert + ) == update_flags::result_inserted; } /// Inserts new node @@ -137,7 +186,15 @@ namespace cds { namespace container { template bool insert( K const& key, V const& val ) { - //TODO + return base_class::do_update( key, key_comparator(), + [&val]( node_type * pNode ) -> mapped_type* + { + assert( pNode->m_pValue.load( memory_model::memory_order_relaxed ) == nullptr ); + CDS_UNUSED( pNode ); + return cxx_allocator().New( val ); + }, + update_flags::allow_insert + ) == update_flags::result_inserted; } /// Inserts new node and initialize it by a functor @@ -146,7 +203,7 @@ namespace cds { namespace container { \p func functor with signature \code struct functor { - void operator()( mapped_type& item ); + void operator()( key_type const& key, mapped_type& item ); }; \endcode @@ -166,7 +223,16 @@ namespace cds { namespace container { template bool insert_with( K const& key, Func func ) { - //TODO + return base_class::do_update( key, key_comparator(), + [&func]( node_type * pNode ) -> mapped_type* + { + assert( pNode->m_pValue.load( memory_model::memory_order_relaxed ) == nullptr ); + mapped_type * pVal = cxx_allocator().New(); + func( pNode->m_key, *pVal ); + return pVal; + }, + update_flags::allow_insert + ) == update_flags::result_inserted; } /// For key \p key inserts data of type \p mapped_type created in-place from \p args @@ -178,42 +244,87 @@ namespace cds { namespace container { template bool emplace( K&& key, Args&&... args ) { - //TODO +# if !( CDS_COMPILER == CDS_COMPILER_GCC && CDS_COMPILER_VERSION >= 40800 && CDS_COMPILER_VERSION < 40900 ) + // Probably, the following code is not so efficient, since we pass lvalues instead rvalues to lambda + //TODO: study how to pass a parameter pack to a lambda efficiently using perfect forwarding + // see http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#904 - this is what we need + return base_class::do_update( key, key_comparator(), + [&args...]( node_type * pNode ) -> mapped_type * + { + assert( pNode->m_pValue.load( memory_model::memory_order_relaxed ) == nullptr ); + CDS_UNUSED( pNode ); + return cxx_allocator().New( std::forward(args)...); + }, + update_flags::allow_insert + ) == update_flags::result_inserted; +# else + // gcc 4.8 error: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47226 + // workaround (from http://stackoverflow.com/questions/14191989/how-do-i-use-variadic-perfect-forwarding-into-a-lambda) + auto f = std::bind( + []( Args... args) -> mapped_type* { return cxx_allocator().New( std::move(args)...); }, + std::forward(args)... + ); + return base_class::do_update( key, key_comparator(), + [&f]( node_type * pNode ) -> mapped_type * + { + assert( pNode->m_pValue.load( memory_model::memory_order_relaxed ) == nullptr ); + CDS_UNUSED( pNode ); + return f(); + }, + update_flags::allow_insert + ) == update_flags::result_inserted; +# endif } - /// Ensures that the \p key exists in the map + /// Updates the value for \p key /** The operation performs inserting or changing data with lock-free manner. If the \p key not found in the map, then the new item created from \p key - is inserted into the map (note that in this case the \ref key_type should be - constructible from type \p K). + will be inserted into the map iff \p bAllowInsert is \p true + (note that in this case the \ref key_type should be constructible from type \p K). Otherwise, the functor \p func is called with item found. - The functor \p Func may be a functor: + The functor \p Func signature is: \code struct my_functor { - void operator()( bool bNew, mapped_type& item ); + void operator()( bool bNew, key_type const& key, mapped_type& item ); }; \endcode with arguments: - \p bNew - \p true if the item has been inserted, \p false otherwise - - \p item - item of the tree + - \p item - value - The functor may change any fields of the \p item + The functor may change any fields of the \p item. The functor is called under the node lock, + the caller can change any field of \p item. RCU \p synchronize() method can be called. RCU should not be locked. Returns std::pair where \p first is \p true if operation is successfull, \p second is \p true if new item has been added or \p false if the item with \p key - already is in the tree. + already exists. */ template - std::pair ensure( K const& key, Func func ) + std::pair update( K const& key, Func func, bool bAllowInsert = true ) { - //TODO + int result = base_class::do_update( key, key_comparator(), + [&func]( node_type * pNode ) -> mapped_type* + { + mapped_type * pVal = pNode->m_pValue.load( memory_model::memory_order_relaxed ); + if ( !pVal ) { + pVal = cxx_allocator().New(); + func( true, pNode->m_key, *pVal ); + } + else + func( false, pNode->m_key, *pVal ); + return pVal; + }, + (bAllowInsert ? update_flags::allow_insert : 0) | update_flags::allow_update + ); + return std::make_pair( result != 0, (result & update_flags::result_inserted) != 0 ); } + /// Delete \p key from the map /** RCU \p synchronize() method can be called. RCU should not be locked. @@ -223,7 +334,7 @@ namespace cds { namespace container { template bool erase( K const& key ) { - //TODO + return base_class::erase( key ); } /// Deletes the item from the map using \p pred predicate for searching @@ -236,8 +347,7 @@ namespace cds { namespace container { template bool erase_with( K const& key, Less pred ) { - CDS_UNUSED( pred ); - //TODO + return base_class::erase_with( key, pred ); } /// Delete \p key from the map @@ -249,7 +359,7 @@ namespace cds { namespace container { The functor \p Func interface: \code struct extractor { - void operator()(mapped_type& item) { ... } + void operator()(key_type const& key, mapped_type& item) { ... } }; \endcode @@ -260,7 +370,7 @@ namespace cds { namespace container { template bool erase( K const& key, Func f ) { - //TODO + return base_class::erase( key, f ); } /// Deletes the item from the map using \p pred predicate for searching @@ -273,15 +383,17 @@ namespace cds { namespace container { template bool erase_with( K const& key, Less pred, Func f ) { - CDS_UNUSED( pred ); - //TODO + return base_class::erase_with( key, pred, f ); } - /// Extracts an item with minimal key from the map + /// Extracts a value with minimal key from the map /** - Returns \ref cds::urcu::exempt_ptr "exempt_ptr" pointer to the leftmost item. + Returns \p exempt_ptr pointer to the leftmost item. If the set is empty, returns empty \p exempt_ptr. + Note that the function returns only the value for minimal key. + To retrieve its key use \p extract_min( Func ) member function. + @note Due the concurrent nature of the map, the function extracts nearly minimum key. It means that the function gets leftmost leaf of the tree and tries to unlink it. During unlinking, a concurrent thread may insert an item with key less than leftmost item's key. @@ -294,14 +406,65 @@ namespace cds { namespace container { */ exempt_ptr extract_min() { - //TODO + return base_class::extract_min(); + } + + /// Extracts minimal key key and corresponding value + /** + Returns \p exempt_ptr to the leftmost item. + If the tree is empty, returns empty \p exempt_ptr. + + \p Func functor is used to store minimal key. + \p Func has the following signature: + \code + struct functor { + void operator()( key_type const& key ); + }; + \endcode + If the tree is empty, \p f is not called. + Otherwise, is it called with minimal key, the pointer to corresponding value is returned + as \p exempt_ptr. + + @note Due the concurrent nature of the map, the function extracts nearly minimum key. + It means that the function gets leftmost leaf of the tree and tries to unlink it. + During unlinking, a concurrent thread may insert an item with key less than leftmost item's key. + So, the function returns the item with minimum key at the moment of tree traversing. + + RCU \p synchronize method can be called. RCU should NOT be locked. + The function does not free the item. + The deallocator will be implicitly invoked when the returned object is destroyed or when + its \p release() member function is called. + */ + template + exempt_ptr extract_min( Func f ) + { + return base_class::extract_min( f ); + } + + /// Extracts minimal key key and corresponding value + /** + This function is a shortcut for the following call: + \code + key_type key; + exempt_ptr xp = theTree.extract_min( [&key]( key_type const& k ) { key = k; } ); + \endcode + \p key_type should be copy-assignable. The copy of minimal key + is returned in \p min_key argument. + */ + typename std::enable_if< std::is_copy_assignable::value, exempt_ptr >::type + extract_min_key( key_type& min_key ) + { + return base_class::extract_min_key( min_key ); } /// Extracts an item with maximal key from the map /** - Returns \ref cds::urcu::exempt_ptr "exempt_ptr" pointer to the rightmost item. + Returns \p exempt_ptr pointer to the rightmost item. If the set is empty, returns empty \p exempt_ptr. + Note that the function returns only the value for maximal key. + To retrieve its key use \p extract_max( Func ) or \p extract_max_key(key_type&) member function. + @note Due the concurrent nature of the map, the function extracts nearly maximal key. It means that the function gets rightmost leaf of the tree and tries to unlink it. During unlinking, a concurrent thread may insert an item with key great than leftmost item's key. @@ -311,43 +474,88 @@ namespace cds { namespace container { The function does not free the item. The deallocator will be implicitly invoked when the returned object is destroyed or when its \p release() is called. - @note Before reusing \p result object you should call its \p release() method. */ exempt_ptr extract_max() { - //TODO + return base_class::extract_max(); + } + + /// Extracts the maximal key and corresponding value + /** + Returns \p exempt_ptr pointer to the rightmost item. + If the set is empty, returns empty \p exempt_ptr. + + \p Func functor is used to store maximal key. + \p Func has the following signature: + \code + struct functor { + void operator()( key_type const& key ); + }; + \endcode + If the tree is empty, \p f is not called. + Otherwise, is it called with maximal key, the pointer to corresponding value is returned + as \p exempt_ptr. + + @note Due the concurrent nature of the map, the function extracts nearly maximal key. + It means that the function gets rightmost leaf of the tree and tries to unlink it. + During unlinking, a concurrent thread may insert an item with key great than leftmost item's key. + So, the function returns the item with maximum key at the moment of tree traversing. + + RCU \p synchronize method can be called. RCU should NOT be locked. + The function does not free the item. + The deallocator will be implicitly invoked when the returned object is destroyed or when + its \p release() is called. + */ + template + exempt_ptr extract_max( Func f ) + { + return base_class::extract_max( f ); + } + + /// Extracts the maximal key and corresponding value + /** + This function is a shortcut for the following call: + \code + key_type key; + exempt_ptr xp = theTree.extract_max( [&key]( key_type const& k ) { key = k; } ); + \endcode + \p key_type should be copy-assignable. The copy of maximal key + is returned in \p max_key argument. + */ + typename std::enable_if< std::is_copy_assignable::value, exempt_ptr >::type + extract_max_key( key_type& max_key ) + { + return base_class::extract_max_key( max_key ); } /// Extracts an item from the map /** The function searches an item with key equal to \p key in the tree, - unlinks it, and returns \ref cds::urcu::exempt_ptr "exempt_ptr" pointer to an item found. + unlinks it, and returns \p exempt_ptr pointer to a value found. If \p key is not found the function returns an empty \p exempt_ptr. RCU \p synchronize method can be called. RCU should NOT be locked. - The function does not destroy the item found. + The function does not destroy the value found. The dealloctor will be implicitly invoked when the returned object is destroyed or when its \p release() member function is called. */ template exempt_ptr extract( Q const& key ) { - //TODO + return base_class::extract( key ); } /// Extracts an item from the map using \p pred for searching /** - The function is an analog of \p extract(exempt_ptr&, Q const&) + The function is an analog of \p extract(Q const&) but \p pred is used for key compare. - \p Less has the interface like \p std::less and should meet \ref cds_container_EllenBinTreeSet_rcu_less - "predicate requirements". + \p Less has the interface like \p std::less. \p pred must imply the same element order as the comparator used for building the map. */ template - exempt_ptr extract_with( Q const& val, Less pred ) + exempt_ptr extract_with( Q const& key, Less pred ) { - CDS_UNUSED( pred ); - //TODO + return base_class::extract_with( key, pred ); } /// Find the key \p key @@ -356,10 +564,10 @@ namespace cds { namespace container { The interface of \p Func functor is: \code struct functor { - void operator()( mapped_type& item ); + void operator()( key_type const& key, mapped_type& val ); }; \endcode - where \p item is the item found. + where \p val is the item found for \p key The functor is called under node-level lock. The function applies RCU lock internally. @@ -385,7 +593,7 @@ namespace cds { namespace container { return base_class::find_with( key, pred, f ); } - /// Find the key \p key + /// Checks whether the map contains \p key /** The function searches the item with key equal to \p key and returns \p true if it is found, and \p false otherwise. @@ -393,52 +601,21 @@ namespace cds { namespace container { The function applies RCU lock internally. */ template - bool find( K const& key ) + bool contains( K const& key ) { - return base_class::find( key ); + return base_class::contains( key ); } - /// Finds the key \p val using \p pred predicate for searching + /// Checks whether the map contains \p key using \p pred predicate for searching /** - The function is an analog of \p find(K const&) - but \p pred is used for key comparing. + The function is similar to contains( key ) but \p pred is used for key comparing. \p Less functor has the interface like \p std::less. - \p Less must imply the same element order as the comparator used for building the map. + \p Less must imply the same element order as the comparator used for building the set. */ template - bool find_with( K const& key, Less pred ) + bool contains( K const& key, Less pred ) { - return base_class::find_with( key, pred ); - } - - /// Finds \p key and return the item found - /** - The function searches the item with key equal to \p key and returns the pointer to item found. - If \p key is not found it returns \p nullptr. - - RCU should be locked before call the function. - Returned pointer is valid while RCU is locked. - */ - template - mapped_type * get( Q const& key ) const - { - //TODO - } - - /// Finds \p key with \p pred predicate and return the item found - /** - The function is an analog of \p get(Q const&) - but \p pred is used for comparing the keys. - - \p Less functor has the semantics like \p std::less but should take arguments of type \ref value_type - and \p Q in any order. - \p pred must imply the same element order as the comparator used for building the map. - */ - template - mapped_type * get_with( Q const& key, Less pred ) const - { - CDS_UNUSED( pred ); - //TODO + return base_class::contains( key, pred ); } /// Clears the map @@ -474,6 +651,18 @@ namespace cds { namespace container { return base_class::statistics(); } + /// Returns reference to \p sync_monitor object + sync_monitor& monitor() + { + return base_class::monitor(); + } + //@cond + sync_monitor const& monitor() const + { + return base_class::monitor(); + } + //@endcond + /// Checks internal consistency (not atomic, not thread-safe) /** The debugging function to check internal consistency of the tree. @@ -483,7 +672,29 @@ namespace cds { namespace container { return base_class::check_consistency(); } + /// Checks internal consistency (not atomic, not thread-safe) + /** + The debugging function to check internal consistency of the tree. + The functor \p Func is called if a violation of internal tree structure + is found: + \code + struct functor { + void operator()( size_t nLevel, size_t hLeft, size_t hRight ); + }; + \endcode + where + - \p nLevel - the level where the violation is found + - \p hLeft - the height of left subtree + - \p hRight - the height of right subtree + + The functor is called for each violation found. + */ + template + bool check_consistency( Func f ) const + { + return base_class::check_consistency( f ); + } }; }} // namespace cds::container -#endif // #ifndef __CDS_CONTAINER_IMPL_BRONSON_AVLTREE_MAP_RCU_H +#endif // #ifndef CDSLIB_CONTAINER_IMPL_BRONSON_AVLTREE_MAP_RCU_H