// bdlma_concurrentfixedpool.h -*-C++-*- // ---------------------------------------------------------------------------- // NOTICE // // This component is not up to date with current BDE coding standards, and // should not be used as an example for new development. // ---------------------------------------------------------------------------- #ifndef INCLUDED_BDLMA_CONCURRENTFIXEDPOOL #define INCLUDED_BDLMA_CONCURRENTFIXEDPOOL //@PURPOSE: Provide thread-safe pool of limited # of blocks of uniform size. // //@CLASSES: // bdlma::ConcurrentFixedPool: thread-safe pool of limited number of blocks // //@SEE_ALSO: bdlma_concurrentpool // //@DESCRIPTION: This component implements a *fully thread-safe* memory pool // that allocates and manages a limited number (specified at construction) of // memory blocks of some uniform size (also specified at construction). A // 'bdlma::ConcurrentFixedPool' constructed to manage up to 'N' blocks also // provides an association between the address of each block and an index in // the range '[ 0 .. N - 1 ]'. // // Other than this mapping between block and index, and the associated limit on // the maximum number of blocks that may be simultaneously allocated, this // component's semantics are identical to 'bdlma::ConcurrentPool'. In // particular, this component overloads global operator 'new' in the same // manner, and the behaviors of 'release' and 'reserveCapacity' are equivalent // to the corresponding methods in 'bdlma::ConcurrentPool'. // // Like 'bdlma::ConcurrentPool', this component is intended to be used to // implement *out-of-place* container classes that hold elements of uniform // size. // ///Usage ///----- // 'bdlma::ConcurrentFixedPool' is intended to implement *out-of-place* // container classes that hold up to a fixed number of elements, all of uniform // size. Suppose we wish to implement a simple thread pool. We want the // equivalent of a 'bsl::deque<bsl::function<void(void)> >'. However, to // minimize the time spent performing operations on this deque - which must be // carried out under a lock - we instead store just pointers in the deque, and // manage memory efficiently using 'bdlma::ConcurrentFixedPool'. // 'bdlma::ConcurrentFixedPool' is fully thread-safe and does not require any // additional synchronization. // // The example below is just for the container portion of our simple thread // pool. The implementation of the worker thread, and the requisite // synchronization, are omitted for clarity. //.. // class my_JobQueue { // // public: // // PUBLIC TYPES // typedef bsl::function<void(void)> Job; // // private: // // DATA // bslmt::Mutex d_lock; // bsl::deque<Job *> d_queue; // bdlma::ConcurrentFixedPool d_pool; // bslma::Allocator *d_allocator_p; // // // Not implemented: // my_JobQueue(const my_JobQueue&); // // public: // // CREATORS // my_JobQueue(int maxJobs, bslma::Allocator *basicAllocator = 0); // ~my_JobQueue(); // // // MANIPULATORS // void enqueueJob(const Job& job); // // int tryExecuteJob(); // }; // // my_JobQueue::my_JobQueue(int maxJobs, bslma::Allocator *basicAllocator) // : d_queue(basicAllocator) // , d_pool(sizeof(Job), maxJobs, basicAllocator) // , d_allocator_p(bslma::Default::allocator(basicAllocator)) // { // } // // my_JobQueue::~my_JobQueue() // { // Job *jobPtr; // while (!d_queue.empty()) { // jobPtr = d_queue.front(); // jobPtr->~Job(); // d_queue.pop_front(); // } // } // // void my_JobQueue::enqueueJob(const Job& job) // { // Job *jobPtr = new (d_pool) Job(job, d_allocator_p); // d_lock.lock(); // d_queue.push_back(jobPtr); // d_lock.unlock(); // } // // int my_JobQueue::tryExecuteJob() // { // d_lock.lock(); // if (d_queue.empty()) { // d_lock.unlock(); // return -1; // RETURN // } // Job *jobPtr = d_queue.front(); // d_queue.pop_front(); // d_lock.unlock(); // (*jobPtr)(); // d_pool.deleteObject(jobPtr); // return 0; // } //.. // Note that in the destructor, there is no need to deallocate the individual // job objects - the destructor of 'bdlma::ConcurrentFixedPool' will release // any remaining allocated memory. However, it *is* necessary to invoke the // destructors of all these objects, as the destructor of // 'bdlma::ConcurrentFixedPool' will not do so. #include <bdlscm_version.h> #include <bslmt_mutex.h> #include <bsls_atomic.h> #include <bdlma_pool.h> #include <bslma_allocator.h> #include <bslma_deleterhelper.h> #include <bsls_alignmentutil.h> #include <bsls_assert.h> #include <bsl_vector.h> #include <bsl_cstdlib.h> namespace BloombergLP { namespace bdlma { // =============================== // struct ConcurrentFixedPool_Node // =============================== struct ConcurrentFixedPool_Node { // The component-private 'struct' provides a header for blocks that are // allocated from 'ConcurrentFixedPool' objects. // DATA unsigned d_next; // index of next free node when on free list; otherwise, // index of this node itself adjusted with a generation // count }; // ========================= // class ConcurrentFixedPool // ========================= class ConcurrentFixedPool { // This class implements a memory pool that allocates and manages up to a // fixed number of memory blocks of some uniform size, with both the limit // on the number of blocks and the block size specified at construction. // // This class guarantees thread safety when allocating or releasing memory // (but see the documentation for the 'release' method). // PRIVATE TYPES typedef ConcurrentFixedPool_Node Node; // type of memory block "header" // DATA bsls::AtomicInt d_freeList; // head of free list const unsigned d_sizeMask; // mask corresponding to max size // of pool; rounded up to power of // 2 bsl::vector<Node *> d_nodes; // holds nodes currently being // pooled; enables index <-> // address mapping const int d_dataOffset; // offset (in bytes) to memory // block within a 'Node' const int d_nodeSize; // size of blocks pooled by // 'd_nodePool' bslmt::Mutex d_nodePoolMutex; // mutex for access to 'd_nodePool' bdlma::Pool d_nodePool; // underlying memory pool int d_numNodes; // number of nodes in 'd_nodes' // that are currently being pooled const int d_objectSize; // size of pooled objects as // specified at construction int d_backoffLevel; // determines amount of spinning // when under contention // NOT IMPLEMENTED ConcurrentFixedPool(const ConcurrentFixedPool&); ConcurrentFixedPool& operator=(const ConcurrentFixedPool&); private: // PRIVATE MANIPULATORS void *allocateNew(); // Allocate a memory block of the 'objectSize' specified at // construction from the underlying pool from which this fixed pool // obtains memory. Return the address of that block or 0 if this pool // is exhausted (i.e., 'poolSize()' memory blocks have already been // allocated from this pool). public: // CREATORS ConcurrentFixedPool(int objectSize, int poolSize, bslma::Allocator *basicAllocator = 0); // Create a memory pool that returns memory of the specified // 'objectSize' for each invocation of the 'allocate' method. // Configure this pool to support allocation of up to the specified // 'poolSize' number of memory blocks. The largest supported // 'poolSize' is 33554431. Optionally specify a 'basicAllocator' used // to supply memory. If 'basicAllocator' is 0, the currently installed // default allocator is used. The behavior is undefined unless // '0 < objectSize', '0 < poolSize', and '0x1FFFFFF >= poolSize'. ~ConcurrentFixedPool(); // Destroy this object and release all associated memory. // MANIPULATORS void *allocate(); // Allocate a memory block of the 'objectSize' specified at // construction. Return the address of that block or 0 if the pool is // exhausted (i.e., 'poolSize()' memory blocks have already been // allocated from this pool). void deallocate(void *address); // Deallocate the memory block at the specified 'address' back to this // pool for reuse. template<class TYPE> void deleteObject(const TYPE *object); // Destroy the specified 'object' based on its dynamic type and then // use this allocator to deallocate its memory footprint. Do nothing // if 'object' is 0. The behavior is undefined unless 'object', when // cast appropriately to 'void *', was allocated using this allocator // and has not already been deallocated. Note that // 'dynamic_cast<void *>(object)' is applied if 'TYPE' is polymorphic, // and 'static_cast<void *>(object)' is applied otherwise. template <class TYPE> void deleteObjectRaw(const TYPE *object); // Destroy the specified 'object' based on its static type and then use // this allocator to deallocate its memory footprint. Do nothing if // 'object' is 0. The behavior is undefined if 'object' is a // base-class pointer to a derived type, was not allocated using this // allocator, or has already been deallocated. void release(); // Release all memory currently allocated through this object. Note // that this method should only be invoked when it is known that no // blocks currently allocated through this pool will be used; // therefore, it is not safe to use this method if any other thread may // be concurrently allocating memory from this pool. Also note that // 'release()' is intended to free all memory without regard to the // contents of that memory. Specifically, 'release()' can *not* call // object destructors for any allocated objects, since it has no // knowledge of their type. If object destruction is required, use // 'ConcurrentFixedPool::deleteObject()'. int reserveCapacity(int numObjects); // Reserve memory from this pool to satisfy memory requests for at // least the specified 'numObjects' before the pool replenishes. The // behavior is undefined unless '0 <= numObjects'. Return 0 on success // and the number of objects that could not be reserved otherwise. // Note that this method fails if the number of memory blocks already // allocated plus 'numObjects' exceeds 'poolSize()'. void setBackoffLevel(int backoffLevel); // Configure this pool with the specified non-negative 'backoffLevel' // that controls the amount of spinning that occurs when calls to this // pool encounter contention. Setting 'backoffLevel' to 0 disables // spinning. Greater values of 'backoffLevel' correspond to greater // amounts of spinning. The behavior is undefined unless // '0 <= backoffLevel'. Note that both contention detection and // spinning strategy are implementation defined. // ACCESSORS int backoffLevel() const; // Return the non-negative 'backoffLevel' that controls the amount of // spinning that occurs when calls to this pool encounter contention. int indexFromAddress(void *address) const; // Return an index in the range from 0 to the maximum size of this pool // that uniquely identifies the memory block at the specified // 'address'. The behavior is undefined unless 'address' corresponds // to a memory block allocated from this pool. int objectSize() const; // Return the size of the memory blocks allocated from this object. // Note that all blocks have the same size. void *addressFromIndex(int index) const; // Return the address of the memory block identified by the specified // 'index'. The behavior is undefined unless the index has been // obtained through 'indexFromAddress'. int poolSize() const; // Return the maximum size of this pool. // Aspects bslma::Allocator *allocator() const; // Return the allocator used by this object to allocate memory. Note // that this allocator can not be used to deallocate memory // allocated through this pool. }; } // close package namespace } // close enterprise namespace // FREE OPERATORS inline void *operator new(bsl::size_t size, BloombergLP::bdlma::ConcurrentFixedPool& pool); // Allocate memory of the specified 'size' bytes from the specified 'pool', // and return the address of the allocated memory. The behavior is // undefined unless 'size' is the same as the 'objectSize' with which // 'pool' was constructed. Note that an object may allocate additional // memory internally within its constructor, requiring the allocator to be // passed in as a constructor argument: //.. // my_Type *newMyType(bdlma::ConcurrentFixedPool *pool, // bslma::Allocator *basicAllocator) { // return new (*pool) my_Type(..., basicAllocator); // } //.. // Note also that the analogous version of operator 'delete' should not be // called directly. Instead, this component provides a template member // function 'bdlma::ConcurrentFixedPool::deleteObject' parameterized by // 'TYPE' that performs the equivalent of the following: //.. // void deleteMyType(bdlma::ConcurrentFixedPool *pool, my_Type *t) { // t->~my_Type(); // pool->deallocate(t); // } //.. inline void operator delete(void *address, BloombergLP::bdlma::ConcurrentFixedPool& pool); // Use the specified 'pool' to deallocate the memory at the specified // 'address'. The behavior is undefined unless 'address' was allocated // using 'pool' and has not already been deallocated. This operator is // supplied solely to allow the compiler to arrange for it to be called in // case of an exception. Client code should not call it; use // 'bdlma::ConcurrentFixedPool::deleteObject()' instead. namespace BloombergLP { namespace bdlma { // ============================================================================ // INLINE DEFINITIONS // ============================================================================ // ------------------------- // class ConcurrentFixedPool // ------------------------- // MANIPULATORS template<class TYPE> inline void ConcurrentFixedPool::deleteObject(const TYPE *object) { bslma::DeleterHelper::deleteObject(object, this); } template<class TYPE> inline void ConcurrentFixedPool::deleteObjectRaw(const TYPE *object) { bslma::DeleterHelper::deleteObjectRaw(object, this); } inline void ConcurrentFixedPool::setBackoffLevel(int backoffLevel) { d_backoffLevel = backoffLevel; } // ACCESSORS inline void *ConcurrentFixedPool::addressFromIndex(int index) const { Node * node = const_cast<Node *>(d_nodes[index]); BSLS_ASSERT(node); return (char *)node + d_dataOffset; } inline int ConcurrentFixedPool::backoffLevel() const { return d_backoffLevel; } inline int ConcurrentFixedPool::indexFromAddress(void *address) const { const Node * const node = (const Node *)(void *) ((char *)address - d_dataOffset); return (node->d_next & d_sizeMask) - 1; } inline int ConcurrentFixedPool::objectSize() const { return d_objectSize; } inline int ConcurrentFixedPool::poolSize() const { return static_cast<int>(d_nodes.size()); } // Aspects inline bslma::Allocator *ConcurrentFixedPool::allocator() const { return d_nodePool.allocator(); } } // close package namespace } // close enterprise namespace inline void *operator new(bsl::size_t size, BloombergLP::bdlma::ConcurrentFixedPool& pool) { using namespace BloombergLP; BSLS_ASSERT((int) size <= pool.objectSize() && bsls::AlignmentUtil::calculateAlignmentFromSize((int)size) <= bsls::AlignmentUtil::calculateAlignmentFromSize(pool.objectSize())); (void)size; // suppress "unused parameter" warnings return pool.allocate(); } inline void operator delete(void *address, BloombergLP::bdlma::ConcurrentFixedPool& pool) { pool.deallocate(address); } #endif // ---------------------------------------------------------------------------- // Copyright 2015 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------