From: khizmax Date: Sat, 15 Nov 2014 13:32:40 +0000 (+0300) Subject: Hazard Pointer GC refactoring X-Git-Tag: v2.0.0~87 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=9b4289e2ef6efad125925b14ae6cdcc670ce4d67;p=libcds.git Hazard Pointer GC refactoring --- diff --git a/cds/gc/details/hp.h b/cds/gc/details/hp.h new file mode 100644 index 00000000..0e9f0c0d --- /dev/null +++ b/cds/gc/details/hp.h @@ -0,0 +1,766 @@ +//$$CDS-header$$ + +#ifndef __CDS_GC_DETAILS_HP_H +#define __CDS_GC_DETAILS_HP_H + +#include +#include +#include + +#include +#include + +#if CDS_COMPILER == CDS_COMPILER_MSVC +# pragma warning(push) + // warning C4251: 'cds::gc::hp::GarbageCollector::m_pListHead' : class 'cds::cxx11_atomic::atomic' + // needs to have dll-interface to be used by clients of class 'cds::gc::hp::GarbageCollector' +# pragma warning(disable: 4251) +#endif + +/* + Editions: + 2007.12.24 khizmax Add statistics and CDS_GATHER_HAZARDPTR_STAT macro + 2008.03.06 khizmax Refactoring: implementation of HazardPtrMgr is moved to hazardptr.cpp + 2008.03.08 khizmax Remove HazardPtrMgr singleton. Now you must initialize/destroy HazardPtrMgr calling + HazardPtrMgr::Construct / HazardPtrMgr::Destruct before use (usually in main() function). + 2008.12.06 khizmax Refactoring. Changes class name, namespace hierarchy, all helper defs have been moved to details namespace + 2010.01.27 khizmax Introducing memory order constraint +*/ + +namespace cds { + /** + @page cds_garbage_collectors_comparison GC comparison + @ingroup cds_garbage_collector + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Feature%cds::gc::HP%cds::gc::DHP
Implementation qualitystablestable
Performance rank (1 - slowest, 5 - fastest)54
Max number of guarded (hazard) pointers per threadlimited (specifies in GC object ctor)unlimited (dynamically allocated when needed)
Max number of retired pointers1boundedbounded
Array of retired pointerspreallocated for each thread, limited in sizeglobal for the entire process, unlimited (dynamically allocated when needed)
Support direct pointer to item of lock-free container (useful for iterators)not supportednot supported
+ + 1Unbounded count of retired pointer means a possibility of memory exhaustion. + */ + + /// Different safe memory reclamation schemas (garbage collectors) + /** @ingroup cds_garbage_collector + + This namespace specifies different safe memory reclamation (SMR) algorithms. + See \ref cds_garbage_collector "Garbage collectors" + */ + namespace gc { + + /// Michael's Hazard Pointers reclamation schema + /** + \par Sources: + - [2002] Maged M.Michael "Safe memory reclamation for dynamic lock-freeobjects using atomic reads and writes" + - [2003] Maged M.Michael "Hazard Pointers: Safe memory reclamation for lock-free objects" + - [2004] Andrei Alexandrescy, Maged Michael "Lock-free Data Structures with Hazard Pointers" + + The \p cds::gc::hp namespace and its members are internal representation of Hazard Pointer GC and should not be used directly. + Use \p cds::gc::HP class in your code. + + Hazard Pointer garbage collector is a singleton. The main user-level part of Hazard Pointer schema is + GC class and its nested classes. Before use any HP-related class you must initialize HP garbage collector + by contructing \p cds::gc::HP object in beginning of your \p main(). + See \p cds::gc::HP class for explanation. + */ + namespace hp { + + // forwards + class GarbageCollector; + class ThreadGC; + + namespace details { + + /// Retired pointer + typedef cds::gc::details::retired_ptr retired_ptr; + + /// Array of retired pointers + /** + The vector of retired pointer ready to delete. + + The Hazard Pointer schema is build on thread-static arrays. For each HP-enabled thread the HP manager allocates + array of retired pointers. The array belongs to the thread: owner thread writes to the array, other threads + just read it. + */ + class retired_vector { + /// Underlying vector implementation + typedef cds::details::bounded_array retired_vector_impl; + + retired_vector_impl m_arr ; ///< the array of retired pointers + size_t m_nSize ; ///< Current size of \p m_arr + + public: + /// Iterator + typedef retired_vector_impl::iterator iterator; + + /// Constructor + retired_vector( const cds::gc::hp::GarbageCollector& HzpMgr ) CDS_NOEXCEPT; // inline + ~retired_vector() + {} + + /// Vector capacity. + /** + The capacity is constant for any thread. It is defined by cds::gc::hp::GarbageCollector. + */ + size_t capacity() const CDS_NOEXCEPT + { + return m_arr.capacity(); + } + + /// Current vector size (count of retired pointers in the vector) + size_t size() const CDS_NOEXCEPT + { + return m_nSize; + } + + /// Set vector size. Uses internally + void size( size_t nSize ) + { + assert( nSize <= capacity() ); + m_nSize = nSize; + } + + /// Pushes retired pointer to the vector + void push( const retired_ptr& p ) + { + assert( m_nSize < capacity() ); + m_arr[ m_nSize ] = p; + ++m_nSize; + } + + /// Checks if the vector is full (size() == capacity() ) + bool isFull() const CDS_NOEXCEPT + { + return m_nSize >= capacity(); + } + + /// Begin iterator + iterator begin() CDS_NOEXCEPT + { + return m_arr.begin(); + } + + /// End iterator + iterator end() CDS_NOEXCEPT + { + return m_arr.begin() + m_nSize ; + } + + /// Clears the vector. After clearing, size() == 0 + void clear() CDS_NOEXCEPT + { + m_nSize = 0; + } + }; + + /// Hazard pointer record of the thread + /** + The structure of type "single writer - multiple reader": only the owner thread may write to this structure + other threads have read-only access. + */ + struct hp_record { + hp_allocator<> m_hzp; ///< array of hazard pointers. Implicit \ref CDS_DEFAULT_ALLOCATOR dependency + retired_vector m_arrRetired ; ///< Retired pointer array + + /// Ctor + hp_record( const cds::gc::hp::GarbageCollector& HzpMgr ); // inline + ~hp_record() + {} + + /// Clears all hazard pointers + void clear() + { + m_hzp.clear(); + } + }; + } // namespace details + + /// GarbageCollector::Scan phase strategy + /** + See GarbageCollector::Scan for explanation + */ + enum scan_type { + classic, ///< classic scan as described in Michael's works (see GarbageCollector::classic_scan) + inplace ///< inplace scan without allocation (see GarbageCollector::inplace_scan) + }; + + /// Hazard Pointer singleton + /** + Safe memory reclamation schema by Michael "Hazard Pointers" + + \par Sources: + \li [2002] Maged M.Michael "Safe memory reclamation for dynamic lock-freeobjects using atomic reads and writes" + \li [2003] Maged M.Michael "Hazard Pointers: Safe memory reclamation for lock-free objects" + \li [2004] Andrei Alexandrescy, Maged Michael "Lock-free Data Structures with Hazard Pointers" + + */ + class CDS_EXPORT_API GarbageCollector + { + public: + typedef cds::atomicity::event_counter event_counter ; ///< event counter type + + /// Internal GC statistics + struct InternalState { + size_t nHPCount ; ///< HP count per thread (const) + size_t nMaxThreadCount ; ///< Max thread count (const) + size_t nMaxRetiredPtrCount ; ///< Max retired pointer count per thread (const) + size_t nHPRecSize ; ///< Size of HP record, bytes (const) + + size_t nHPRecAllocated ; ///< Count of HP record allocations + size_t nHPRecUsed ; ///< Count of HP record used + size_t nTotalRetiredPtrCount ; ///< Current total count of retired pointers + size_t nRetiredPtrInFreeHPRecs ; ///< Count of retired pointer in free (unused) HP records + + event_counter::value_type evcAllocHPRec ; ///< Count of \p hp_record allocations + event_counter::value_type evcRetireHPRec ; ///< Count of \p hp_record retire events + event_counter::value_type evcAllocNewHPRec; ///< Count of new \p hp_record allocations from heap + event_counter::value_type evcDeleteHPRec ; ///< Count of \p hp_record deletions + + event_counter::value_type evcScanCall ; ///< Count of Scan calling + event_counter::value_type evcHelpScanCall ; ///< Count of HelpScan calling + event_counter::value_type evcScanFromHelpScan;///< Count of Scan calls from HelpScan + + event_counter::value_type evcDeletedNode ; ///< Count of deleting of retired objects + event_counter::value_type evcDeferredNode ; ///< Count of objects that cannot be deleted in Scan phase because of a hazard_pointer guards it + }; + + /// No GarbageCollector object is created + CDS_DECLARE_EXCEPTION( HZPManagerEmpty, "Global Hazard Pointer GarbageCollector is NULL" ); + + /// Not enough required Hazard Pointer count + CDS_DECLARE_EXCEPTION( HZPTooMany, "Not enough required Hazard Pointer count" ); + + private: + /// Internal GC statistics + struct Statistics { + event_counter m_AllocHPRec ; ///< Count of \p hp_record allocations + event_counter m_RetireHPRec ; ///< Count of \p hp_record retire events + event_counter m_AllocNewHPRec ; ///< Count of new \p hp_record allocations from heap + event_counter m_DeleteHPRec ; ///< Count of \p hp_record deletions + + event_counter m_ScanCallCount ; ///< Count of Scan calling + event_counter m_HelpScanCallCount ; ///< Count of HelpScan calling + event_counter m_CallScanFromHelpScan ; ///< Count of Scan calls from HelpScan + + event_counter m_DeletedNode ; ///< Count of retired objects deleting + event_counter m_DeferredNode ; ///< Count of objects that cannot be deleted in Scan phase because of a hazard_pointer guards it + }; + + /// Internal list of cds::gc::hp::details::hp_record + struct hplist_node : public details::hp_record + { + hplist_node * m_pNextNode ; ///< next hazard ptr record in list + atomics::atomic m_idOwner ; ///< Owner thread id; 0 - the record is free (not owned) + atomics::atomic m_bFree ; ///< true if record if free (not owned) + + //@cond + hplist_node( const GarbageCollector& HzpMgr ) + : hp_record( HzpMgr ), + m_pNextNode( nullptr ), + m_idOwner( OS::c_NullThreadId ), + m_bFree( true ) + {} + + ~hplist_node() + { + assert( m_idOwner.load( atomics::memory_order_relaxed ) == OS::c_NullThreadId ); + assert( m_bFree.load(atomics::memory_order_relaxed) ); + } + //@endcond + }; + + atomics::atomic m_pListHead ; ///< Head of GC list + + static GarbageCollector * m_pHZPManager ; ///< GC instance pointer + + Statistics m_Stat ; ///< Internal statistics + bool m_bStatEnabled ; ///< true - statistics enabled + + const size_t m_nHazardPointerCount ; ///< max count of thread's hazard pointer + const size_t m_nMaxThreadCount ; ///< max count of thread + const size_t m_nMaxRetiredPtrCount ; ///< max count of retired ptr per thread + scan_type m_nScanType ; ///< scan type (see \ref scan_type enum) + + + private: + /// Ctor + GarbageCollector( + size_t nHazardPtrCount = 0, ///< Hazard pointer count per thread + size_t nMaxThreadCount = 0, ///< Max count of thread + size_t nMaxRetiredPtrCount = 0, ///< Capacity of the array of retired objects + scan_type nScanType = inplace ///< Scan type (see \ref scan_type enum) + ); + + /// Dtor + ~GarbageCollector(); + + /// Allocate new HP record + hplist_node * NewHPRec(); + + /// Permanently deletes HPrecord \p pNode + /** + Caveat: for performance reason this function is defined as inline and cannot be called directly + */ + void DeleteHPRec( hplist_node * pNode ); + + /// Permanently deletes retired pointer \p p + /** + Caveat: for performance reason this function is defined as inline and cannot be called directly + */ + void DeletePtr( details::retired_ptr& p ); + + //@cond + void detachAllThread(); + //@endcond + + public: + /// Creates GarbageCollector singleton + /** + GC is the singleton. If GC instance is not exist then the function creates the instance. + Otherwise it does nothing. + + The Michael's HP reclamation schema depends of three parameters: + + \p nHazardPtrCount - HP pointer count per thread. Usually it is small number (2-4) depending from + the data structure algorithms. By default, if \p nHazardPtrCount = 0, + the function uses maximum of HP count for CDS library. + + \p nMaxThreadCount - max count of thread with using HP GC in your application. Default is 100. + + \p nMaxRetiredPtrCount - capacity of array of retired pointers for each thread. Must be greater than + \p nHazardPtrCount * \p nMaxThreadCount. + Default is 2 * \p nHazardPtrCount * \p nMaxThreadCount. + */ + static void CDS_STDCALL Construct( + size_t nHazardPtrCount = 0, ///< Hazard pointer count per thread + size_t nMaxThreadCount = 0, ///< Max count of simultaneous working thread in your application + size_t nMaxRetiredPtrCount = 0, ///< Capacity of the array of retired objects for the thread + scan_type nScanType = inplace ///< Scan type (see \ref scan_type enum) + ); + + /// Destroys global instance of GarbageCollector + /** + The parameter \p bDetachAll should be used carefully: if its value is \p true, + then the destroying GC automatically detaches all attached threads. This feature + can be useful when you have no control over the thread termination, for example, + when \p libcds is injected into existing external thread. + */ + static void CDS_STDCALL Destruct( + bool bDetachAll = false ///< Detach all threads + ); + + /// Returns pointer to GarbageCollector instance + static GarbageCollector& instance() + { + if ( !m_pHZPManager ) + throw HZPManagerEmpty(); + return *m_pHZPManager; + } + + /// Checks if global GC object is constructed and may be used + static bool isUsed() CDS_NOEXCEPT + { + return m_pHZPManager != nullptr; + } + + /// Returns max Hazard Pointer count defined in construction time + size_t getHazardPointerCount() const CDS_NOEXCEPT + { + return m_nHazardPointerCount; + } + + /// Returns max thread count defined in construction time + size_t getMaxThreadCount() const CDS_NOEXCEPT + { + return m_nMaxThreadCount; + } + + /// Returns max size of retired objects array. It is defined in construction time + size_t getMaxRetiredPtrCount() const CDS_NOEXCEPT + { + return m_nMaxRetiredPtrCount; + } + + // Internal statistics + + /// Get internal statistics + InternalState& getInternalState(InternalState& stat) const; + + /// Checks if internal statistics enabled + bool isStatisticsEnabled() const { return m_bStatEnabled; } + + /// Enables/disables internal statistics + bool enableStatistics( bool bEnable ) + { + bool bEnabled = m_bStatEnabled; + m_bStatEnabled = bEnable; + return bEnabled; + } + + /// Checks that required hazard pointer count \p nRequiredCount is less or equal then max hazard pointer count + /** + If \p nRequiredCount > getHazardPointerCount() then the exception HZPTooMany is thrown + */ + static void checkHPCount( unsigned int nRequiredCount ) + { + if ( instance().getHazardPointerCount() < nRequiredCount ) + throw HZPTooMany(); + } + + /// Get current scan strategy + scan_type getScanType() const + { + return m_nScanType; + } + + /// Set current scan strategy + /** @anchor hzp_gc_setScanType + Scan strategy changing is allowed on the fly. + */ + void setScanType( + scan_type nScanType ///< new scan strategy + ) + { + m_nScanType = nScanType; + } + + public: // Internals for threads + + /// Allocates Hazard Pointer GC record. For internal use only + details::hp_record * alloc_hp_record(); + + /// Free HP record. For internal use only + void free_hp_record( details::hp_record * pRec ); + + /// The main garbage collecting function + /** + This function is called internally by ThreadGC object when upper bound of thread's list of reclaimed pointers + is reached. + + There are the following scan algorithm: + - \ref hzp_gc_classic_scan "classic_scan" allocates memory for internal use + - \ref hzp_gc_inplace_scan "inplace_scan" does not allocate any memory + + Use \ref hzp_gc_setScanType "setScanType" member function to setup appropriate scan algorithm. + */ + void Scan( details::hp_record * pRec ) + { + switch ( m_nScanType ) { + case inplace: + inplace_scan( pRec ); + break; + default: + assert(false) ; // Forgotten something?.. + case classic: + classic_scan( pRec ); + break; + } + } + + /// Helper scan routine + /** + The function guarantees that every node that is eligible for reuse is eventually freed, barring + thread failures. To do so, after executing Scan, a thread executes a HelpScan, + where it checks every HP record. If an HP record is inactive, the thread moves all "lost" reclaimed pointers + to thread's list of reclaimed pointers. + + The function is called internally by Scan. + */ + void HelpScan( details::hp_record * pThis ); + + protected: + /// Classic scan algorithm + /** @anchor hzp_gc_classic_scan + Classical scan algorithm as described in Michael's paper. + + A scan includes four stages. The first stage involves scanning the array HP for non-null values. + Whenever a non-null value is encountered, it is inserted in a local list of currently protected pointer. + Only stage 1 accesses shared variables. The following stages operate only on private variables. + + The second stage of a scan involves sorting local list of protected pointers to allow + binary search in the third stage. + + The third stage of a scan involves checking each reclaimed node + against the pointers in local list of protected pointers. If the binary search yields + no match, the node is freed. Otherwise, it cannot be deleted now and must kept in thread's list + of reclaimed pointers. + + The forth stage prepares new thread's private list of reclaimed pointers + that could not be freed during the current scan, where they remain until the next scan. + + This algorithm allocates memory for internal HP array. + + This function is called internally by ThreadGC object when upper bound of thread's list of reclaimed pointers + is reached. + */ + void classic_scan( details::hp_record * pRec ); + + /// In-place scan algorithm + /** @anchor hzp_gc_inplace_scan + Unlike the \ref hzp_gc_classic_scan "classic_scan" algorithm, \p inplace_scan does not allocate any memory. + All operations are performed in-place. + */ + void inplace_scan( details::hp_record * pRec ); + }; + + /// Thread's hazard pointer manager + /** + To use Hazard Pointer reclamation schema each thread object must be linked with the object of ThreadGC class + that interacts with GarbageCollector global object. The linkage is performed by calling \ref cds_threading "cds::threading::Manager::attachThread()" + on the start of each thread that uses HP GC. Before terminating the thread linked to HP GC it is necessary to call + \ref cds_threading "cds::threading::Manager::detachThread()". + */ + class ThreadGC + { + GarbageCollector& m_HzpManager; ///< Hazard Pointer GC singleton + details::hp_record * m_pHzpRec; ///< Pointer to thread's HZP record + + public: + /// Default constructor + ThreadGC() + : m_HzpManager( GarbageCollector::instance() ), + m_pHzpRec( nullptr ) + {} + + /// The object is not copy-constructible + ThreadGC( ThreadGC const& ) = delete; + + ~ThreadGC() + { + fini(); + } + + /// Checks if thread GC is initialized + bool isInitialized() const { return m_pHzpRec != nullptr; } + + /// Initialization. Repeat call is available + void init() + { + if ( !m_pHzpRec ) + m_pHzpRec = m_HzpManager.alloc_hp_record(); + } + + /// Finalization. Repeat call is available + void fini() + { + if ( m_pHzpRec ) { + details::hp_record * pRec = m_pHzpRec; + m_pHzpRec = nullptr; + m_HzpManager.free_hp_record( pRec ); + } + } + + /// Initializes HP guard \p guard + details::hp_guard& allocGuard() + { + assert( m_pHzpRec ); + return m_pHzpRec->m_hzp.alloc(); + } + + /// Frees HP guard \p guard + void freeGuard( details::hp_guard& guard ) + { + assert( m_pHzpRec ); + m_pHzpRec->m_hzp.free( guard ); + } + + /// Initializes HP guard array \p arr + template + void allocGuard( details::hp_array& arr ) + { + assert( m_pHzpRec ); + m_pHzpRec->m_hzp.alloc( arr ); + } + + /// Frees HP guard array \p arr + template + void freeGuard( details::hp_array& arr ) + { + assert( m_pHzpRec ); + m_pHzpRec->m_hzp.free( arr ); + } + + /// Places retired pointer \p and its deleter \p pFunc into thread's array of retired pointer for deferred reclamation + template + void retirePtr( T * p, void (* pFunc)(T *) ) + { + /* + union { + T * p; + hazard_pointer hp; + } cast_ptr; + cast_ptr.p = p; + + uinion{ + void( *pFunc )(T *); + free_retired_ptr_func hpFunc; + } cast_func; + cast_func.pFunc = pFunc; + + retirePtr( details::retired_ptr( cast_ptr.hp, cast_func.hpFunc ) ); + */ + retirePtr( details::retired_ptr( reinterpret_cast( p ), reinterpret_cast( pFunc ) ) ); + } + + /// Places retired pointer \p into thread's array of retired pointer for deferred reclamation + void retirePtr( details::retired_ptr const& p ) + { + m_pHzpRec->m_arrRetired.push( p ); + + if ( m_pHzpRec->m_arrRetired.isFull() ) { + // Max of retired pointer count is reached. Do scan + scan(); + } + } + + //@cond + void scan() + { + m_HzpManager.Scan( m_pHzpRec ); + m_HzpManager.HelpScan( m_pHzpRec ); + } + //@endcond + }; + + /// Auto hp_guard. + /** + This class encapsulates Hazard Pointer guard to protect a pointer against deletion . + It allocates one HP from thread's HP array in constructor and free the hazard pointer allocated + in destructor. + */ + class guard + { + //@cond + details::hp_guard& m_hp ; ///< Hazard pointer guarded + ThreadGC& m_gc ; ///< Thread GC + //@endcond + + public: + typedef details::hp_guard::hazard_ptr hazard_ptr ; ///< Hazard pointer type + public: + /// Allocates HP guard from \p gc + guard( ThreadGC& gc ) + : m_hp( gc.allocGuard() ) + , m_gc( gc ) + {} + + /// Allocates HP guard from \p gc and protects the pointer \p p of type \p T + template + guard( ThreadGC& gc, T * p ) + : m_hp( gc.allocGuard() ) + , m_gc( gc ) + { + m_hp = p; + } + + /// Frees HP guard. The pointer guarded may be deleted after this. + ~guard() + { + m_gc.freeGuard( m_hp ); + } + + /// Returns thread GC + ThreadGC& getGC() const + { + return m_gc; + } + + /// Protects the pointer \p p against reclamation (guards the pointer). + template + T * operator =( T * p ) + { + return m_hp = p; + } + + //@cond + std::nullptr_t operator =(std::nullptr_t) + { + return m_hp = nullptr; + } + + hazard_ptr get() const + { + return m_hp; + } + //@endcond + }; + + /// Auto-managed array of hazard pointers + /** + This class is wrapper around cds::gc::hp::details::hp_array class. + \p Count is the size of HP array + */ + template + class array : public details::hp_array + { + ThreadGC& m_mgr ; ///< Thread GC + + public: + /// Rebind array for other size \p COUNT2 + template + struct rebind { + typedef array other; ///< rebinding result + }; + + public: + /// Allocates array of HP guard from \p mgr + array( ThreadGC& mgr ) + : m_mgr( mgr ) + { + mgr.allocGuard( *this ); + } + + /// Frees array of HP guard + ~array() + { + m_mgr.freeGuard( *this ); + } + + /// Returns thread GC + ThreadGC& getGC() const { return m_mgr; } + }; + + } // namespace hp +}} // namespace cds::gc + +// Inlines +#include + +#if CDS_COMPILER == CDS_COMPILER_MSVC +# pragma warning(pop) +#endif + +#endif // #ifndef __CDS_GC_DETAILS_HP_H diff --git a/cds/gc/details/hp_alloc.h b/cds/gc/details/hp_alloc.h new file mode 100644 index 00000000..e057e1ba --- /dev/null +++ b/cds/gc/details/hp_alloc.h @@ -0,0 +1,320 @@ +//$$CDS-header$$ + +#ifndef __CDS_GC_DETAILS_HP_ALLOC_H +#define __CDS_GC_DETAILS_HP_ALLOC_H + +#include +#include +#include + +//@cond +namespace cds { + namespace gc { namespace hp { + // forwards + class GarbageCollector; + class ThreadGC; + + /// Hazard Pointer schema implementation details + namespace details { + + /// Hazard pointer guard + /** + It is unsafe to use this class directly. + Instead, the \p hp::guard class should be used. + */ + class hp_guard : protected atomics::atomic < hazard_pointer > + { + public: + typedef hazard_pointer hazard_ptr; ///< Hazard pointer type + private: + //@cond + typedef atomics::atomic base_class; + //@endcond + + protected: + //@cond + template friend class hp_allocator; + //@endcond + + public: + hp_guard() CDS_NOEXCEPT + : base_class( nullptr ) + {} + ~hp_guard() CDS_NOEXCEPT + {} + + /// Sets HP value. Guards pointer \p p from reclamation. + /** + Storing has release semantics. + */ + template + T * operator =(T * p) CDS_NOEXCEPT + { + // We use atomic store with explicit memory order because other threads may read this hazard pointer concurrently + set( p ); + return p; + } + + //@cond + std::nullptr_t operator=(std::nullptr_t) CDS_NOEXCEPT + { + clear(); + return nullptr; + } + //@endcond + + /// Returns current value of hazard pointer + /** + Loading has acquire semantics + */ + operator hazard_ptr() const CDS_NOEXCEPT + { + return get(); + } + + /// Returns current value of hazard pointer + /** + Loading has acquire semantics + */ + hazard_ptr get( atomics::memory_order order = atomics::memory_order_acquire ) const CDS_NOEXCEPT + { + return base_class::load( order ); + } + + template + void set( T * p, atomics::memory_order order = atomics::memory_order_release ) CDS_NOEXCEPT + { + base_class::store( reinterpret_cast(p), order ); + } + + /// Clears HP + /** + Clearing has relaxed semantics. + */ + void clear( atomics::memory_order order = atomics::memory_order_relaxed ) CDS_NOEXCEPT + { + // memory order is not necessary here + base_class::store( nullptr, order ); + } + }; + + /// Array of hazard pointers. + /** + Array of hazard-pointer. Placing a pointer into this array guards the pointer against reclamation. + Template parameter \p Count defines the size of hazard pointer array. \p Count parameter should not exceed + GarbageCollector::getHazardPointerCount(). + + It is unsafe to use this class directly. Instead, the \p hp::array should be used. + + While creating the object of \p hp_array class an array of size \p Count of hazard pointers is reserved by + the HP Manager of current thread. The object's destructor cleans all of reserved hazard pointer and + returns reserved HP to the HP pool of ThreadGC. + + Usually, it is not necessary to create an object of this class. The object of class ThreadGC contains + the \p hp_array object and implements interface for HP setting and freeing. + + Template parameter: + \li Count - capacity of array + + */ + template + class hp_array + { + public: + typedef hazard_pointer hazard_ptr_type; ///< Hazard pointer type + typedef hp_guard atomic_hazard_ptr; ///< Element type of the array + static CDS_CONSTEXPR const size_t c_nCapacity = Count ; ///< Capacity of the array + + private: + //@cond + atomic_hazard_ptr * m_arr ; ///< Hazard pointer array of size = \p Count + template friend class hp_allocator; + //@endcond + + public: + /// Constructs uninitialized array. + hp_array() CDS_NOEXCEPT + {} + + /// Destructs object + ~hp_array() CDS_NOEXCEPT + {} + + /// Returns max count of hazard pointer for this array + CDS_CONSTEXPR size_t capacity() const + { + return c_nCapacity; + } + + /// Set hazard pointer \p nIndex. 0 <= \p nIndex < \p Count + void set( size_t nIndex, hazard_ptr_type hzPtr ) CDS_NOEXCEPT + { + assert( nIndex < capacity() ); + m_arr[nIndex] = hzPtr; + } + + /// Returns reference to hazard pointer of index \p nIndex (0 <= \p nIndex < \p Count) + atomic_hazard_ptr& operator []( size_t nIndex ) CDS_NOEXCEPT + { + assert( nIndex < capacity() ); + return m_arr[nIndex]; + } + + /// Returns reference to hazard pointer of index \p nIndex (0 <= \p nIndex < \p Count) [const version] + atomic_hazard_ptr& operator []( size_t nIndex ) const CDS_NOEXCEPT + { + assert( nIndex < capacity() ); + return m_arr[nIndex]; + } + + /// Clears (sets to \p nullptr) hazard pointer \p nIndex + void clear( size_t nIndex ) CDS_NOEXCEPT + { + assert( nIndex < capacity() ); + m_arr[ nIndex ].clear(); + } + }; + + /// Allocator of hazard pointers for the thread + /** + The hazard pointer array is the free-list of unused hazard pointer for the thread. + The array is managed as a stack. + The max size (capacity) of array is defined at ctor time and cannot be changed during object's lifetime + + Each allocator object is thread-private. + + Template parameters: + \li Allocator - memory allocator class, default is \ref CDS_DEFAULT_ALLOCATOR + + This helper class should not be used directly. + */ + template + class hp_allocator + { + public: + typedef hazard_pointer hazard_ptr_type; ///< type of hazard pointer + typedef hp_guard atomic_hazard_ptr; ///< Atomic hazard pointer type + typedef Allocator allocator_type; ///< allocator type + + private: + //@cond + typedef cds::details::Allocator< atomic_hazard_ptr, allocator_type > allocator_impl; + + atomic_hazard_ptr * m_arrHazardPtr ; ///< Array of hazard pointers + size_t m_nTop ; ///< The top of stack + const size_t m_nCapacity ; ///< Array capacity + + //@endcond + + public: + /// Default ctor + explicit hp_allocator( + size_t nCapacity ///< max count of hazard pointer per thread + ) + : m_arrHazardPtr( alloc_array( nCapacity ) ) + , m_nCapacity( nCapacity ) + { + make_free(); + } + + /// Dtor + ~hp_allocator() + { + allocator_impl().Delete( m_arrHazardPtr, capacity() ); + } + + /// Get capacity of array + size_t capacity() const CDS_NOEXCEPT + { + return m_nCapacity; + } + + /// Get size of array. The size is equal to the capacity of array + size_t size() const CDS_NOEXCEPT + { + return capacity(); + } + + /// Checks if all items are allocated + bool isFull() const CDS_NOEXCEPT + { + return m_nTop == 0; + } + + /// Allocates hazard pointer + atomic_hazard_ptr& alloc() CDS_NOEXCEPT + { + assert( m_nTop > 0 ); + --m_nTop; + return m_arrHazardPtr[m_nTop]; + } + + /// Frees previously allocated hazard pointer + void free( atomic_hazard_ptr& hp ) CDS_NOEXCEPT + { + assert( m_nTop < capacity() ); + hp.clear(); + ++m_nTop; + CDS_COMPILER_RW_BARRIER ; // ??? + } + + /// Allocates hazard pointers array + /** + Allocates \p Count hazard pointers from array \p m_arrHazardPtr + Returns initialized object \p arr + */ + template + void alloc( hp_array& arr ) CDS_NOEXCEPT + { + assert( m_nTop >= Count ); + m_nTop -= Count; + arr.m_arr = m_arrHazardPtr + m_nTop; + } + + /// Frees hazard pointer array + /** + Frees the array of hazard pointers allocated by previous call \p this->alloc. + */ + template + void free( hp_array const& arr ) CDS_NOEXCEPT + { + assert( m_nTop + Count <= capacity()); + for ( size_t i = m_nTop; i < m_nTop + Count; ++i ) + m_arrHazardPtr[ i ].clear(); + m_nTop += Count; + } + + /// Makes all HP free + void clear() CDS_NOEXCEPT + { + make_free(); + } + + /// Returns to i-th hazard pointer + atomic_hazard_ptr& operator []( size_t i ) CDS_NOEXCEPT + { + assert( i < capacity() ); + return m_arrHazardPtr[i]; + } + + private: + //@cond + void make_free() CDS_NOEXCEPT + { + for ( size_t i = 0; i < capacity(); ++i ) + m_arrHazardPtr[ i ].clear(); + m_nTop = capacity(); + } + + atomic_hazard_ptr * alloc_array( size_t nCapacity ) + { + return allocator_impl().NewArray( nCapacity ); + } + //@endcond + }; + + }}} // namespace gc::hp::details +} // namespace cds +//@endcond + +#endif // #ifndef __CDS_GC_DETAILS_HP_ALLOC_H diff --git a/cds/gc/details/hp_inline.h b/cds/gc/details/hp_inline.h new file mode 100644 index 00000000..86bc997c --- /dev/null +++ b/cds/gc/details/hp_inline.h @@ -0,0 +1,26 @@ +//$$CDS-header$$ + +#ifndef __CDS_GC_DETAILS_HP_INLINE_H +#define __CDS_GC_DETAILS_HP_INLINE_H + +namespace cds { + namespace gc{ namespace hp { namespace details { + + /************************************************************************/ + /* INLINES */ + /************************************************************************/ + inline retired_vector::retired_vector( const cds::gc::hp::GarbageCollector& HzpMgr ) CDS_NOEXCEPT + : m_arr( HzpMgr.getMaxRetiredPtrCount() ), + m_nSize(0) + {} + + inline hp_record::hp_record( const cds::gc::hp::GarbageCollector& HzpMgr ) + : m_hzp( HzpMgr.getHazardPointerCount() ), + m_arrRetired( HzpMgr ) + {} + + } } } // namespace gc::hp::details +} // namespace cds + + +#endif // #ifndef __CDS_GC_DETAILS_HP_INLINE_H diff --git a/cds/gc/details/hp_type.h b/cds/gc/details/hp_type.h new file mode 100644 index 00000000..21671216 --- /dev/null +++ b/cds/gc/details/hp_type.h @@ -0,0 +1,23 @@ +//$$CDS-header$$ + +#ifndef __CDS_GC_DETAILS_HP_TYPE_H +#define __CDS_GC_DETAILS_HP_TYPE_H + +#include // free_retired_ptr_func + +namespace cds { + namespace gc { + namespace hp { + + /// Hazard pointer + typedef void * hazard_pointer; + + /// Pointer to function to free (destruct and deallocate) retired pointer of specific type + typedef cds::gc::details::free_retired_ptr_func free_retired_ptr_func; + } + } +} + +#endif // #ifndef __CDS_GC_DETAILS_HP_TYPE_H + + diff --git a/cds/gc/hp/details/hp_alloc.h b/cds/gc/hp/details/hp_alloc.h deleted file mode 100644 index 05cc6260..00000000 --- a/cds/gc/hp/details/hp_alloc.h +++ /dev/null @@ -1,320 +0,0 @@ -//$$CDS-header$$ - -#ifndef __CDS_GC_HP_DETAILS_HP_ALLOC_H -#define __CDS_GC_HP_DETAILS_HP_ALLOC_H - -#include -#include -#include - -//@cond -namespace cds { - namespace gc { namespace hp { - // forwards - class GarbageCollector; - class ThreadGC; - - /// Hazard Pointer schema implementation details - namespace details { - - /// Hazard pointer guard - /** - It is unsafe to use this class directly. - Instead, the AutoHPGuard class should be used. - */ - class hp_guard : protected atomics::atomic < hazard_pointer > - { - public: - typedef hazard_pointer hazard_ptr; ///< Hazard pointer type - private: - //@cond - typedef atomics::atomic base_class; - //@endcond - - protected: - //@cond - template friend class hp_allocator; - //@endcond - - public: - hp_guard() CDS_NOEXCEPT - : base_class( nullptr ) - {} - ~hp_guard() CDS_NOEXCEPT - {} - - /// Sets HP value. Guards pointer \p p from reclamation. - /** - Storing has release semantics. - */ - template - T * operator =(T * p) CDS_NOEXCEPT - { - // We use atomic store with explicit memory order because other threads may read this hazard pointer concurrently - set( p ); - return p; - } - - //@cond - std::nullptr_t operator=(std::nullptr_t) CDS_NOEXCEPT - { - clear(); - return nullptr; - } - //@endcond - - /// Returns current value of hazard pointer - /** - Loading has acquire semantics - */ - operator hazard_ptr() const CDS_NOEXCEPT - { - return get(); - } - - /// Returns current value of hazard pointer - /** - Loading has acquire semantics - */ - hazard_ptr get( atomics::memory_order order = atomics::memory_order_acquire ) const CDS_NOEXCEPT - { - return base_class::load( order ); - } - - template - void set( T * p, atomics::memory_order order = atomics::memory_order_release ) CDS_NOEXCEPT - { - base_class::store( reinterpret_cast(p), order ); - } - - /// Clears HP - /** - Clearing has relaxed semantics. - */ - void clear( atomics::memory_order order = atomics::memory_order_relaxed ) CDS_NOEXCEPT - { - // memory order is not necessary here - base_class::store( nullptr, order ); - } - }; - - /// Array of hazard pointers. - /** - Array of hazard-pointer. Placing a pointer into this array guards the pointer against reclamation. - Template parameter \p Count defines the size of hazard pointer array. \p Count parameter should not exceed - GarbageCollector::getHazardPointerCount(). - - It is unsafe to use this class directly. Instead, the AutoHPArray should be used. - - While creating the object of \p hp_array class an array of size \p Count of hazard pointers is reserved by - the HP Manager of current thread. The object's destructor cleans all of reserved hazard pointer and - returns reserved HP to the HP pool of ThreadGC. - - Usually, it is not necessary to create an object of this class. The object of class ThreadGC contains - the \p hp_array object and implements interface for HP setting and freeing. - - Template parameter: - \li Count - capacity of array - - */ - template - class hp_array - { - public: - typedef hazard_pointer hazard_ptr_type; ///< Hazard pointer type - typedef hp_guard atomic_hazard_ptr; ///< Element type of the array - static CDS_CONSTEXPR const size_t c_nCapacity = Count ; ///< Capacity of the array - - private: - //@cond - atomic_hazard_ptr * m_arr ; ///< Hazard pointer array of size = \p Count - template friend class hp_allocator; - //@endcond - - public: - /// Constructs uninitialized array. - hp_array() CDS_NOEXCEPT - {} - - /// Destructs object - ~hp_array() CDS_NOEXCEPT - {} - - /// Returns max count of hazard pointer for this array - CDS_CONSTEXPR size_t capacity() const - { - return c_nCapacity; - } - - /// Set hazard pointer \p nIndex. 0 <= \p nIndex < \p Count - void set( size_t nIndex, hazard_ptr_type hzPtr ) CDS_NOEXCEPT - { - assert( nIndex < capacity() ); - m_arr[nIndex] = hzPtr; - } - - /// Returns reference to hazard pointer of index \p nIndex (0 <= \p nIndex < \p Count) - atomic_hazard_ptr& operator []( size_t nIndex ) CDS_NOEXCEPT - { - assert( nIndex < capacity() ); - return m_arr[nIndex]; - } - - /// Returns reference to hazard pointer of index \p nIndex (0 <= \p nIndex < \p Count) [const version] - atomic_hazard_ptr& operator []( size_t nIndex ) const CDS_NOEXCEPT - { - assert( nIndex < capacity() ); - return m_arr[nIndex]; - } - - /// Clears (sets to \p nullptr) hazard pointer \p nIndex - void clear( size_t nIndex ) CDS_NOEXCEPT - { - assert( nIndex < capacity() ); - m_arr[ nIndex ].clear(); - } - }; - - /// Allocator of hazard pointers for the thread - /** - The hazard pointer array is the free-list of unused hazard pointer for the thread. - The array is managed as a stack. - The max size (capacity) of array is defined at ctor time and cannot be changed during object's lifetime - - Each allocator object is thread-private. - - Template parameters: - \li Allocator - memory allocator class, default is \ref CDS_DEFAULT_ALLOCATOR - - This helper class should not be used directly. - */ - template - class hp_allocator - { - public: - typedef hazard_pointer hazard_ptr_type; ///< type of hazard pointer - typedef hp_guard atomic_hazard_ptr; ///< Atomic hazard pointer type - typedef Allocator allocator_type; ///< allocator type - - private: - //@cond - typedef cds::details::Allocator< atomic_hazard_ptr, allocator_type > allocator_impl; - - atomic_hazard_ptr * m_arrHazardPtr ; ///< Array of hazard pointers - size_t m_nTop ; ///< The top of stack - const size_t m_nCapacity ; ///< Array capacity - - //@endcond - - public: - /// Default ctor - explicit hp_allocator( - size_t nCapacity ///< max count of hazard pointer per thread - ) - : m_arrHazardPtr( alloc_array( nCapacity ) ) - , m_nCapacity( nCapacity ) - { - make_free(); - } - - /// Dtor - ~hp_allocator() - { - allocator_impl().Delete( m_arrHazardPtr, capacity() ); - } - - /// Get capacity of array - size_t capacity() const CDS_NOEXCEPT - { - return m_nCapacity; - } - - /// Get size of array. The size is equal to the capacity of array - size_t size() const CDS_NOEXCEPT - { - return capacity(); - } - - /// Checks if all items are allocated - bool isFull() const CDS_NOEXCEPT - { - return m_nTop == 0; - } - - /// Allocates hazard pointer - atomic_hazard_ptr& alloc() CDS_NOEXCEPT - { - assert( m_nTop > 0 ); - --m_nTop; - return m_arrHazardPtr[m_nTop]; - } - - /// Frees previously allocated hazard pointer - void free( atomic_hazard_ptr& hp ) CDS_NOEXCEPT - { - assert( m_nTop < capacity() ); - hp.clear(); - ++m_nTop; - CDS_COMPILER_RW_BARRIER ; // ??? - } - - /// Allocates hazard pointers array - /** - Allocates \p Count hazard pointers from array \p m_arrHazardPtr - Returns initialized object \p arr - */ - template - void alloc( hp_array& arr ) CDS_NOEXCEPT - { - assert( m_nTop >= Count ); - m_nTop -= Count; - arr.m_arr = m_arrHazardPtr + m_nTop; - } - - /// Frees hazard pointer array - /** - Frees the array of hazard pointers allocated by previous call \p this->alloc. - */ - template - void free( hp_array const& arr ) CDS_NOEXCEPT - { - assert( m_nTop + Count <= capacity()); - for ( size_t i = m_nTop; i < m_nTop + Count; ++i ) - m_arrHazardPtr[ i ].clear(); - m_nTop += Count; - } - - /// Makes all HP free - void clear() CDS_NOEXCEPT - { - make_free(); - } - - /// Returns to i-th hazard pointer - atomic_hazard_ptr& operator []( size_t i ) CDS_NOEXCEPT - { - assert( i < capacity() ); - return m_arrHazardPtr[i]; - } - - private: - //@cond - void make_free() CDS_NOEXCEPT - { - for ( size_t i = 0; i < capacity(); ++i ) - m_arrHazardPtr[ i ].clear(); - m_nTop = capacity(); - } - - atomic_hazard_ptr * alloc_array( size_t nCapacity ) - { - return allocator_impl().NewArray( nCapacity ); - } - //@endcond - }; - - }}} // namespace gc::hp::details -} // namespace cds -//@endcond - -#endif // #ifndef __CDS_GC_HP_DETAILS_HP_ALLOC_H diff --git a/cds/gc/hp/details/hp_inline.h b/cds/gc/hp/details/hp_inline.h deleted file mode 100644 index 7142b780..00000000 --- a/cds/gc/hp/details/hp_inline.h +++ /dev/null @@ -1,26 +0,0 @@ -//$$CDS-header$$ - -#ifndef __CDS_GC_HP_DETAILS_HP_INLINE_H -#define __CDS_GC_HP_DETAILS_HP_INLINE_H - -namespace cds { - namespace gc{ namespace hp { namespace details { - - /************************************************************************/ - /* INLINES */ - /************************************************************************/ - inline retired_vector::retired_vector( const cds::gc::hp::GarbageCollector& HzpMgr ) CDS_NOEXCEPT - : m_arr( HzpMgr.getMaxRetiredPtrCount() ), - m_nSize(0) - {} - - inline hp_record::hp_record( const cds::gc::hp::GarbageCollector& HzpMgr ) - : m_hzp( HzpMgr.getHazardPointerCount() ), - m_arrRetired( HzpMgr ) - {} - - } } } // namespace gc::hp::details -} // namespace cds - - -#endif // #ifndef __CDS_GC_HP_DETAILS_HP_INLINE_H diff --git a/cds/gc/hp/details/hp_type.h b/cds/gc/hp/details/hp_type.h deleted file mode 100644 index 2f22ebd4..00000000 --- a/cds/gc/hp/details/hp_type.h +++ /dev/null @@ -1,23 +0,0 @@ -//$$CDS-header$$ - -#ifndef __CDS_GC_HP_DETAILS_HP_TYPE_H -#define __CDS_GC_HP_DETAILS_HP_TYPE_H - -#include // free_retired_ptr_func - -namespace cds { - namespace gc { - namespace hp { - - /// Hazard pointer - typedef void * hazard_pointer; - - /// Pointer to function to free (destruct and deallocate) retired pointer of specific type - typedef cds::gc::details::free_retired_ptr_func free_retired_ptr_func; - } - } -} - -#endif // #ifndef __CDS_GC_HP_DETAILS_HP_TYPE_H - - diff --git a/cds/gc/hp/hp.h b/cds/gc/hp/hp.h deleted file mode 100644 index 9124850a..00000000 --- a/cds/gc/hp/hp.h +++ /dev/null @@ -1,750 +0,0 @@ -//$$CDS-header$$ - -#ifndef __CDS_GC_HP_HP_H -#define __CDS_GC_HP_HP_H - -#include -#include -#include - -#include -#include - -#if CDS_COMPILER == CDS_COMPILER_MSVC -# pragma warning(push) - // warning C4251: 'cds::gc::hp::GarbageCollector::m_pListHead' : class 'cds::cxx11_atomic::atomic' - // needs to have dll-interface to be used by clients of class 'cds::gc::hp::GarbageCollector' -# pragma warning(disable: 4251) -#endif - -/* - Editions: - 2007.12.24 khizmax Add statistics and CDS_GATHER_HAZARDPTR_STAT macro - 2008.03.06 khizmax Refactoring: implementation of HazardPtrMgr is moved to hazardptr.cpp - 2008.03.08 khizmax Remove HazardPtrMgr singleton. Now you must initialize/destroy HazardPtrMgr calling - HazardPtrMgr::Construct / HazardPtrMgr::Destruct before use (usually in main() function). - 2008.12.06 khizmax Refactoring. Changes class name, namespace hierarchy, all helper defs have been moved to details namespace - 2010.01.27 khizmax Introducing memory order constraint -*/ - -namespace cds { - /** - @page cds_garbage_collectors_comparison GC comparison - @ingroup cds_garbage_collector - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Feature%cds::gc::HP%cds::gc::DHP
Implementation qualitystablestable
Performance rank (1 - slowest, 5 - fastest)54
Max number of guarded (hazard) pointers per threadlimited (specifies in GC object ctor)unlimited (dynamically allocated when needed)
Max number of retired pointers1boundedbounded
Array of retired pointerspreallocated for each thread, limited in sizeglobal for the entire process, unlimited (dynamically allocated when needed)
Support direct pointer to item of lock-free container (useful for iterators)not supportednot supported
- - 1Unbounded count of retired pointer means a possibility of memory exhaustion. - */ - - /// Different safe memory reclamation schemas (garbage collectors) - /** @ingroup cds_garbage_collector - - This namespace specifies different safe memory reclamation (SMR) algorithms. - See \ref cds_garbage_collector "Garbage collectors" - */ - namespace gc { - - /// Michael's Hazard Pointers reclamation schema - /** - \par Sources: - - [2002] Maged M.Michael "Safe memory reclamation for dynamic lock-freeobjects using atomic reads and writes" - - [2003] Maged M.Michael "Hazard Pointers: Safe memory reclamation for lock-free objects" - - [2004] Andrei Alexandrescy, Maged Michael "Lock-free Data Structures with Hazard Pointers" - - The \p cds::gc::hp namespace and its members are internal representation of Hazard Pointer GC and should not be used directly. - Use \p cds::gc::HP class in your code. - - Hazard Pointer garbage collector is a singleton. The main user-level part of Hazard Pointer schema is - GC class and its nested classes. Before use any HP-related class you must initialize HP garbage collector - by contructing \p cds::gc::HP object in beginning of your \p main(). - See \p cds::gc::HP class for explanation. - */ - namespace hp { - - // forwards - class GarbageCollector; - class ThreadGC; - - namespace details { - - /// Retired pointer - typedef cds::gc::details::retired_ptr retired_ptr; - - /// Array of retired pointers - /** - The vector of retired pointer ready to delete. - - The Hazard Pointer schema is build on thread-static arrays. For each HP-enabled thread the HP manager allocates - array of retired pointers. The array belongs to the thread: owner thread writes to the array, other threads - just read it. - */ - class retired_vector { - /// Underlying vector implementation - typedef cds::details::bounded_array retired_vector_impl; - - retired_vector_impl m_arr ; ///< the array of retired pointers - size_t m_nSize ; ///< Current size of \p m_arr - - public: - /// Iterator - typedef retired_vector_impl::iterator iterator; - - /// Constructor - retired_vector( const cds::gc::hp::GarbageCollector& HzpMgr ) CDS_NOEXCEPT; // inline - ~retired_vector() - {} - - /// Vector capacity. - /** - The capacity is constant for any thread. It is defined by cds::gc::hp::GarbageCollector. - */ - size_t capacity() const CDS_NOEXCEPT - { - return m_arr.capacity(); - } - - /// Current vector size (count of retired pointers in the vector) - size_t size() const CDS_NOEXCEPT - { - return m_nSize; - } - - /// Set vector size. Uses internally - void size( size_t nSize ) - { - assert( nSize <= capacity() ); - m_nSize = nSize; - } - - /// Pushes retired pointer to the vector - void push( const retired_ptr& p ) - { - assert( m_nSize < capacity() ); - m_arr[ m_nSize ] = p; - ++m_nSize; - } - - /// Checks if the vector is full (size() == capacity() ) - bool isFull() const CDS_NOEXCEPT - { - return m_nSize >= capacity(); - } - - /// Begin iterator - iterator begin() CDS_NOEXCEPT - { - return m_arr.begin(); - } - - /// End iterator - iterator end() CDS_NOEXCEPT - { - return m_arr.begin() + m_nSize ; - } - - /// Clears the vector. After clearing, size() == 0 - void clear() CDS_NOEXCEPT - { - m_nSize = 0; - } - }; - - /// Hazard pointer record of the thread - /** - The structure of type "single writer - multiple reader": only the owner thread may write to this structure - other threads have read-only access. - */ - struct hp_record { - hp_allocator<> m_hzp; ///< array of hazard pointers. Implicit \ref CDS_DEFAULT_ALLOCATOR dependency - retired_vector m_arrRetired ; ///< Retired pointer array - - /// Ctor - hp_record( const cds::gc::hp::GarbageCollector& HzpMgr ); // inline - ~hp_record() - {} - - /// Clears all hazard pointers - void clear() - { - m_hzp.clear(); - } - }; - } // namespace details - - /// GarbageCollector::Scan phase strategy - /** - See GarbageCollector::Scan for explanation - */ - enum scan_type { - classic, ///< classic scan as described in Michael's works (see GarbageCollector::classic_scan) - inplace ///< inplace scan without allocation (see GarbageCollector::inplace_scan) - }; - - /// Hazard Pointer singleton - /** - Safe memory reclamation schema by Michael "Hazard Pointers" - - \par Sources: - \li [2002] Maged M.Michael "Safe memory reclamation for dynamic lock-freeobjects using atomic reads and writes" - \li [2003] Maged M.Michael "Hazard Pointers: Safe memory reclamation for lock-free objects" - \li [2004] Andrei Alexandrescy, Maged Michael "Lock-free Data Structures with Hazard Pointers" - - */ - class CDS_EXPORT_API GarbageCollector - { - public: - typedef cds::atomicity::event_counter event_counter ; ///< event counter type - - /// Internal GC statistics - struct InternalState { - size_t nHPCount ; ///< HP count per thread (const) - size_t nMaxThreadCount ; ///< Max thread count (const) - size_t nMaxRetiredPtrCount ; ///< Max retired pointer count per thread (const) - size_t nHPRecSize ; ///< Size of HP record, bytes (const) - - size_t nHPRecAllocated ; ///< Count of HP record allocations - size_t nHPRecUsed ; ///< Count of HP record used - size_t nTotalRetiredPtrCount ; ///< Current total count of retired pointers - size_t nRetiredPtrInFreeHPRecs ; ///< Count of retired pointer in free (unused) HP records - - event_counter::value_type evcAllocHPRec ; ///< Count of \p hp_record allocations - event_counter::value_type evcRetireHPRec ; ///< Count of \p hp_record retire events - event_counter::value_type evcAllocNewHPRec; ///< Count of new \p hp_record allocations from heap - event_counter::value_type evcDeleteHPRec ; ///< Count of \p hp_record deletions - - event_counter::value_type evcScanCall ; ///< Count of Scan calling - event_counter::value_type evcHelpScanCall ; ///< Count of HelpScan calling - event_counter::value_type evcScanFromHelpScan;///< Count of Scan calls from HelpScan - - event_counter::value_type evcDeletedNode ; ///< Count of deleting of retired objects - event_counter::value_type evcDeferredNode ; ///< Count of objects that cannot be deleted in Scan phase because of a hazard_pointer guards it - }; - - /// No GarbageCollector object is created - CDS_DECLARE_EXCEPTION( HZPManagerEmpty, "Global Hazard Pointer GarbageCollector is NULL" ); - - /// Not enough required Hazard Pointer count - CDS_DECLARE_EXCEPTION( HZPTooMany, "Not enough required Hazard Pointer count" ); - - private: - /// Internal GC statistics - struct Statistics { - event_counter m_AllocHPRec ; ///< Count of \p hp_record allocations - event_counter m_RetireHPRec ; ///< Count of \p hp_record retire events - event_counter m_AllocNewHPRec ; ///< Count of new \p hp_record allocations from heap - event_counter m_DeleteHPRec ; ///< Count of \p hp_record deletions - - event_counter m_ScanCallCount ; ///< Count of Scan calling - event_counter m_HelpScanCallCount ; ///< Count of HelpScan calling - event_counter m_CallScanFromHelpScan ; ///< Count of Scan calls from HelpScan - - event_counter m_DeletedNode ; ///< Count of retired objects deleting - event_counter m_DeferredNode ; ///< Count of objects that cannot be deleted in Scan phase because of a hazard_pointer guards it - }; - - /// Internal list of cds::gc::hp::details::hp_record - struct hplist_node : public details::hp_record - { - hplist_node * m_pNextNode ; ///< next hazard ptr record in list - atomics::atomic m_idOwner ; ///< Owner thread id; 0 - the record is free (not owned) - atomics::atomic m_bFree ; ///< true if record if free (not owned) - - //@cond - hplist_node( const GarbageCollector& HzpMgr ) - : hp_record( HzpMgr ), - m_pNextNode( nullptr ), - m_idOwner( OS::c_NullThreadId ), - m_bFree( true ) - {} - - ~hplist_node() - { - assert( m_idOwner.load( atomics::memory_order_relaxed ) == OS::c_NullThreadId ); - assert( m_bFree.load(atomics::memory_order_relaxed) ); - } - //@endcond - }; - - atomics::atomic m_pListHead ; ///< Head of GC list - - static GarbageCollector * m_pHZPManager ; ///< GC instance pointer - - Statistics m_Stat ; ///< Internal statistics - bool m_bStatEnabled ; ///< true - statistics enabled - - const size_t m_nHazardPointerCount ; ///< max count of thread's hazard pointer - const size_t m_nMaxThreadCount ; ///< max count of thread - const size_t m_nMaxRetiredPtrCount ; ///< max count of retired ptr per thread - scan_type m_nScanType ; ///< scan type (see \ref scan_type enum) - - - private: - /// Ctor - GarbageCollector( - size_t nHazardPtrCount = 0, ///< Hazard pointer count per thread - size_t nMaxThreadCount = 0, ///< Max count of thread - size_t nMaxRetiredPtrCount = 0, ///< Capacity of the array of retired objects - scan_type nScanType = inplace ///< Scan type (see \ref scan_type enum) - ); - - /// Dtor - ~GarbageCollector(); - - /// Allocate new HP record - hplist_node * NewHPRec(); - - /// Permanently deletes HPrecord \p pNode - /** - Caveat: for performance reason this function is defined as inline and cannot be called directly - */ - void DeleteHPRec( hplist_node * pNode ); - - /// Permanently deletes retired pointer \p p - /** - Caveat: for performance reason this function is defined as inline and cannot be called directly - */ - void DeletePtr( details::retired_ptr& p ); - - //@cond - void detachAllThread(); - //@endcond - - public: - /// Creates GarbageCollector singleton - /** - GC is the singleton. If GC instance is not exist then the function creates the instance. - Otherwise it does nothing. - - The Michael's HP reclamation schema depends of three parameters: - - \p nHazardPtrCount - HP pointer count per thread. Usually it is small number (2-4) depending from - the data structure algorithms. By default, if \p nHazardPtrCount = 0, - the function uses maximum of HP count for CDS library. - - \p nMaxThreadCount - max count of thread with using HP GC in your application. Default is 100. - - \p nMaxRetiredPtrCount - capacity of array of retired pointers for each thread. Must be greater than - \p nHazardPtrCount * \p nMaxThreadCount. - Default is 2 * \p nHazardPtrCount * \p nMaxThreadCount. - */ - static void CDS_STDCALL Construct( - size_t nHazardPtrCount = 0, ///< Hazard pointer count per thread - size_t nMaxThreadCount = 0, ///< Max count of simultaneous working thread in your application - size_t nMaxRetiredPtrCount = 0, ///< Capacity of the array of retired objects for the thread - scan_type nScanType = inplace ///< Scan type (see \ref scan_type enum) - ); - - /// Destroys global instance of GarbageCollector - /** - The parameter \p bDetachAll should be used carefully: if its value is \p true, - then the destroying GC automatically detaches all attached threads. This feature - can be useful when you have no control over the thread termination, for example, - when \p libcds is injected into existing external thread. - */ - static void CDS_STDCALL Destruct( - bool bDetachAll = false ///< Detach all threads - ); - - /// Returns pointer to GarbageCollector instance - static GarbageCollector& instance() - { - if ( !m_pHZPManager ) - throw HZPManagerEmpty(); - return *m_pHZPManager; - } - - /// Checks if global GC object is constructed and may be used - static bool isUsed() CDS_NOEXCEPT - { - return m_pHZPManager != nullptr; - } - - /// Returns max Hazard Pointer count defined in construction time - size_t getHazardPointerCount() const CDS_NOEXCEPT - { - return m_nHazardPointerCount; - } - - /// Returns max thread count defined in construction time - size_t getMaxThreadCount() const CDS_NOEXCEPT - { - return m_nMaxThreadCount; - } - - /// Returns max size of retired objects array. It is defined in construction time - size_t getMaxRetiredPtrCount() const CDS_NOEXCEPT - { - return m_nMaxRetiredPtrCount; - } - - // Internal statistics - - /// Get internal statistics - InternalState& getInternalState(InternalState& stat) const; - - /// Checks if internal statistics enabled - bool isStatisticsEnabled() const { return m_bStatEnabled; } - - /// Enables/disables internal statistics - bool enableStatistics( bool bEnable ) - { - bool bEnabled = m_bStatEnabled; - m_bStatEnabled = bEnable; - return bEnabled; - } - - /// Checks that required hazard pointer count \p nRequiredCount is less or equal then max hazard pointer count - /** - If \p nRequiredCount > getHazardPointerCount() then the exception HZPTooMany is thrown - */ - static void checkHPCount( unsigned int nRequiredCount ) - { - if ( instance().getHazardPointerCount() < nRequiredCount ) - throw HZPTooMany(); - } - - /// Get current scan strategy - scan_type getScanType() const - { - return m_nScanType; - } - - /// Set current scan strategy - /** @anchor hzp_gc_setScanType - Scan strategy changing is allowed on the fly. - */ - void setScanType( - scan_type nScanType ///< new scan strategy - ) - { - m_nScanType = nScanType; - } - - public: // Internals for threads - - /// Allocates Hazard Pointer GC record. For internal use only - details::hp_record * AllocateHPRec(); - - /// Free HP record. For internal use only - void RetireHPRec( details::hp_record * pRec ); - - /// The main garbage collecting function - /** - This function is called internally by ThreadGC object when upper bound of thread's list of reclaimed pointers - is reached. - - There are the following scan algorithm: - - \ref hzp_gc_classic_scan "classic_scan" allocates memory for internal use - - \ref hzp_gc_inplace_scan "inplace_scan" does not allocate any memory - - Use \ref hzp_gc_setScanType "setScanType" member function to setup appropriate scan algorithm. - */ - void Scan( details::hp_record * pRec ) - { - switch ( m_nScanType ) { - case inplace: - inplace_scan( pRec ); - break; - default: - assert(false) ; // Forgotten something?.. - case classic: - classic_scan( pRec ); - break; - } - } - - /// Helper scan routine - /** - The function guarantees that every node that is eligible for reuse is eventually freed, barring - thread failures. To do so, after executing Scan, a thread executes a HelpScan, - where it checks every HP record. If an HP record is inactive, the thread moves all "lost" reclaimed pointers - to thread's list of reclaimed pointers. - - The function is called internally by Scan. - */ - void HelpScan( details::hp_record * pThis ); - - protected: - /// Classic scan algorithm - /** @anchor hzp_gc_classic_scan - Classical scan algorithm as described in Michael's paper. - - A scan includes four stages. The first stage involves scanning the array HP for non-null values. - Whenever a non-null value is encountered, it is inserted in a local list of currently protected pointer. - Only stage 1 accesses shared variables. The following stages operate only on private variables. - - The second stage of a scan involves sorting local list of protected pointers to allow - binary search in the third stage. - - The third stage of a scan involves checking each reclaimed node - against the pointers in local list of protected pointers. If the binary search yields - no match, the node is freed. Otherwise, it cannot be deleted now and must kept in thread's list - of reclaimed pointers. - - The forth stage prepares new thread's private list of reclaimed pointers - that could not be freed during the current scan, where they remain until the next scan. - - This algorithm allocates memory for internal HP array. - - This function is called internally by ThreadGC object when upper bound of thread's list of reclaimed pointers - is reached. - */ - void classic_scan( details::hp_record * pRec ); - - /// In-place scan algorithm - /** @anchor hzp_gc_inplace_scan - Unlike the \ref hzp_gc_classic_scan "classic_scan" algorithm, \p inplace_scan does not allocate any memory. - All operations are performed in-place. - */ - void inplace_scan( details::hp_record * pRec ); - }; - - /// Thread's hazard pointer manager - /** - To use Hazard Pointer reclamation schema each thread object must be linked with the object of ThreadGC class - that interacts with GarbageCollector global object. The linkage is performed by calling \ref cds_threading "cds::threading::Manager::attachThread()" - on the start of each thread that uses HP GC. Before terminating the thread linked to HP GC it is necessary to call - \ref cds_threading "cds::threading::Manager::detachThread()". - */ - class ThreadGC - { - GarbageCollector& m_HzpManager; ///< Hazard Pointer GC singleton - details::hp_record * m_pHzpRec; ///< Pointer to thread's HZP record - - public: - /// Default constructor - ThreadGC() - : m_HzpManager( GarbageCollector::instance() ), - m_pHzpRec( nullptr ) - {} - - /// The object is not copy-constructible - ThreadGC( ThreadGC const& ) = delete; - - ~ThreadGC() - { - fini(); - } - - /// Checks if thread GC is initialized - bool isInitialized() const { return m_pHzpRec != nullptr; } - - /// Initialization. Repeat call is available - void init() - { - if ( !m_pHzpRec ) - m_pHzpRec = m_HzpManager.AllocateHPRec(); - } - - /// Finalization. Repeat call is available - void fini() - { - if ( m_pHzpRec ) { - details::hp_record * pRec = m_pHzpRec; - m_pHzpRec = nullptr; - m_HzpManager.RetireHPRec( pRec ); - } - } - - /// Initializes HP guard \p guard - details::hp_guard& allocGuard() - { - assert( m_pHzpRec ); - return m_pHzpRec->m_hzp.alloc(); - } - - /// Frees HP guard \p guard - void freeGuard( details::hp_guard& guard ) - { - assert( m_pHzpRec ); - m_pHzpRec->m_hzp.free( guard ); - } - - /// Initializes HP guard array \p arr - template - void allocGuard( details::hp_array& arr ) - { - assert( m_pHzpRec ); - m_pHzpRec->m_hzp.alloc( arr ); - } - - /// Frees HP guard array \p arr - template - void freeGuard( details::hp_array& arr ) - { - assert( m_pHzpRec ); - m_pHzpRec->m_hzp.free( arr ); - } - - /// Places retired pointer \p and its deleter \p pFunc into thread's array of retired pointer for deferred reclamation - template - void retirePtr( T * p, void (* pFunc)(T *) ) - { - retirePtr( details::retired_ptr( reinterpret_cast( p ), reinterpret_cast( pFunc ) ) ); - } - - /// Places retired pointer \p into thread's array of retired pointer for deferred reclamation - void retirePtr( const details::retired_ptr& p ) - { - m_pHzpRec->m_arrRetired.push( p ); - - if ( m_pHzpRec->m_arrRetired.isFull() ) { - // Max of retired pointer count is reached. Do scan - scan(); - } - } - - //@cond - void scan() - { - m_HzpManager.Scan( m_pHzpRec ); - m_HzpManager.HelpScan( m_pHzpRec ); - } - //@endcond - }; - - /// Auto hp_guard. - /** - This class encapsulates Hazard Pointer guard to protect a pointer against deletion . - It allocates one HP from thread's HP array in constructor and free the HP allocated in destruction time. - */ - class AutoHPGuard - { - //@cond - details::hp_guard& m_hp ; ///< Hazard pointer guarded - ThreadGC& m_gc ; ///< Thread GC - //@endcond - - public: - typedef details::hp_guard::hazard_ptr hazard_ptr ; ///< Hazard pointer type - public: - /// Allocates HP guard from \p gc - AutoHPGuard( ThreadGC& gc ) - : m_hp( gc.allocGuard() ) - , m_gc( gc ) - {} - - /// Allocates HP guard from \p gc and protects the pointer \p p of type \p T - template - AutoHPGuard( ThreadGC& gc, T * p ) - : m_hp( gc.allocGuard() ) - , m_gc( gc ) - { - m_hp = p; - } - - /// Frees HP guard. The pointer guarded may be deleted after this. - ~AutoHPGuard() - { - m_gc.freeGuard( m_hp ); - } - - /// Returns thread GC - ThreadGC& getGC() const - { - return m_gc; - } - - /// Protects the pointer \p p against reclamation (guards the pointer). - template - T * operator =( T * p ) - { - return m_hp = p; - } - - //@cond - std::nullptr_t operator =(std::nullptr_t) - { - return m_hp = nullptr; - } - - hazard_ptr get() const - { - return m_hp; - } - //@endcond - }; - - /// Auto-managed array of hazard pointers - /** - This class is wrapper around cds::gc::hp::details::hp_array class. - \p Count is the size of HP array - */ - template - class AutoHPArray : public details::hp_array - { - ThreadGC& m_mgr ; ///< Thread GC - - public: - /// Rebind array for other size \p COUNT2 - template - struct rebind { - typedef AutoHPArray other ; ///< rebinding result - }; - - public: - /// Allocates array of HP guard from \p mgr - AutoHPArray( ThreadGC& mgr ) - : m_mgr( mgr ) - { - mgr.allocGuard( *this ); - } - - /// Frees array of HP guard - ~AutoHPArray() - { - m_mgr.freeGuard( *this ); - } - - /// Returns thread GC - ThreadGC& getGC() const { return m_mgr; } - }; - - } // namespace hp -}} // namespace cds::gc - -// Inlines -#include - -#if CDS_COMPILER == CDS_COMPILER_MSVC -# pragma warning(pop) -#endif - -#endif // #ifndef __CDS_GC_HP_HP_H diff --git a/cds/gc/hp/hp_decl.h b/cds/gc/hp/hp_decl.h index 44ef1d39..9de3da4e 100644 --- a/cds/gc/hp/hp_decl.h +++ b/cds/gc/hp/hp_decl.h @@ -4,7 +4,7 @@ #define __CDS_GC_HP_HP_DECL_H #include // overflow_error -#include +#include #include namespace cds { namespace gc { @@ -86,12 +86,12 @@ namespace cds { namespace gc { /// Hazard Pointer guard /** @headerfile cds/gc/hp.h - This class is a wrapper for hp::AutoHPGuard. + This class is a wrapper for \p hp::guard. */ - class Guard : public hp::AutoHPGuard + class Guard : public hp::guard { //@cond - typedef hp::AutoHPGuard base_class; + typedef hp::guard base_class; //@endcond public: @@ -206,14 +206,14 @@ namespace cds { namespace gc { /// Array of Hazard Pointer guards /** @headerfile cds/gc/hp.h - This class is a wrapper for hp::AutoHPArray template. + This class is a wrapper for \p hp::array template. Template parameter \p Count defines the size of HP array. */ template - class GuardArray : public hp::AutoHPArray + class GuardArray : public hp::array { //@cond - typedef hp::AutoHPArray base_class; + typedef hp::array base_class; //@endcond public: /// Rebind array for other size \p Count2 diff --git a/projects/Win/vc12/cds.vcxproj b/projects/Win/vc12/cds.vcxproj index 99b4958b..3b31649a 100644 --- a/projects/Win/vc12/cds.vcxproj +++ b/projects/Win/vc12/cds.vcxproj @@ -734,14 +734,15 @@ + + + + - - - diff --git a/projects/Win/vc12/cds.vcxproj.filters b/projects/Win/vc12/cds.vcxproj.filters index 788c8b97..d8599743 100644 --- a/projects/Win/vc12/cds.vcxproj.filters +++ b/projects/Win/vc12/cds.vcxproj.filters @@ -1154,15 +1154,6 @@ Header Files\cds\gc - - Header Files\cds\gc\hp - - - Header Files\cds\gc\hp - - - Header Files\cds\gc\hp - Header Files\cds\gc\hp @@ -1184,5 +1175,17 @@ Source Files + + Header Files\cds\gc\details + + + Header Files\cds\gc\details + + + Header Files\cds\gc\details + + + Header Files\cds\gc\details + \ No newline at end of file diff --git a/src/hp_gc.cpp b/src/hp_gc.cpp index a4681b99..4899dd4d 100644 --- a/src/hp_gc.cpp +++ b/src/hp_gc.cpp @@ -9,7 +9,7 @@ 2008.02.10 Maxim.Khiszinsky Created */ -#include +#include #include // std::sort #include "hp_const.h" @@ -103,7 +103,7 @@ namespace cds { namespace gc { p.free(); } - details::hp_record * GarbageCollector::AllocateHPRec() + details::hp_record * GarbageCollector::alloc_hp_record() { CDS_HAZARDPTR_STATISTIC( ++m_Stat.m_AllocHPRec ); @@ -136,7 +136,7 @@ namespace cds { namespace gc { return hprec; } - void GarbageCollector::RetireHPRec( details::hp_record * pRec ) + void GarbageCollector::free_hp_record( details::hp_record * pRec ) { assert( pRec != nullptr ); CDS_HAZARDPTR_STATISTIC( ++m_Stat.m_RetireHPRec ); @@ -154,7 +154,7 @@ namespace cds { namespace gc { for ( hplist_node * hprec = m_pListHead.load(atomics::memory_order_acquire); hprec; hprec = pNext ) { pNext = hprec->m_pNextNode; if ( hprec->m_idOwner.load(atomics::memory_order_relaxed) != nullThreadId ) { - RetireHPRec( hprec ); + free_hp_record( hprec ); } } }