// bdlcc_fixedqueueindexmanager.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_BDLCC_FIXEDQUEUEINDEXMANAGER #define INCLUDED_BDLCC_FIXEDQUEUEINDEXMANAGER #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide thread-enabled state management for a fixed-size queue. // //@CLASSES: // bdlcc::FixedQueueIndexManager: state management for a queue // //@DESCRIPTION: This component implements a lock-free mechanism for managing // the indices of a circular buffer of elements to facilitate the // implementation of a fixed-size thread-enabled single-ended queue. A // 'bdlcc::FixedQueueIndexManager' is supplied the size of a circular buffer on // construction, and provides the methods to reserve indices for enqueing and // dequeing elements in that buffer. The actual buffer is held in some other // (external) data structure managed by the user of this component. // // This component is not *itself* a general-purpose queue data structure. For // example, no user data of any kind is stored in this data structure (it is // not a queue of integers), and successful invocation of certain methods // ('reservePopIndex', 'reservePushIndex') obligates the caller to invoke a // corresponding method ('commitPopIndex', 'commitPushIndex' respectively); // otherwise, other threads may "spin" indefinitely with severe performance // consequences. // ///Thread Safety ///------------- // 'bdlcc::FixedQueueIndexManager' is fully *thread-safe*, meaning that all // non-creator operations on an object can be safely invoked simultaneously // from multiple threads. // ///Exception safety ///---------------- // All methods of the 'bdlcc::FixedQueueIndexManager' provide a no-throw // exception guarantee, except for the constructor, which is exception neutral. // ///Usage ///----- // This section illustrates intended use of this component. // ///Example 1: Creating a Thread-Safe Queue of Integers ///- - - - - - - - - - - - - - - - - - - - - - - - - - // In the following example we create a simple thread-safe queue of integers // using a 'bdlcc::FixedQueueIndexManager' to synchronize the queue operations. // // We start by declaring the data members of an 'IntegerQueue', a vector of // integers, to hold the values in the queue, and an index manager to ensure // thread-safe access to the indices of the vector: //.. // class IntegerQueue { // // This class provides a fully thread-safe queue of integers with a // // fixed maximum capacity. // // // DATA // bdlcc::FixedQueueIndexManager d_indexManager; // manages 'd_values' // // indices // // bsl::vector<int> d_values; // maintains values // // private: // // Not implemented: // IntegerQueue(const IntegerQueue&); // // public: //.. // Then, we declare the methods of an integer queue: //.. // // CREATORS // explicit IntegerQueue(bsl::size_t capacity, // bslma::Allocator *basicAllocator = 0); // // Create a queue capable of holding up to the specified // // 'capacity' number of integer values. // // ~IntegerQueue(); // // Destroy this queue. // // // MANIPULATORS // int tryPushBack(int value); // // Attempt to push the specified 'value' onto the back of this // // queue. Return 0 on success, and a non-zero value if this queue // // is full. // // int tryPopFront(int *result); // // Attempt to remove an element from the front of this queue and // // load the removed value into the specified 'result'. Return 0 // // on success, and a non-zero value otherwise. // // // ACCESSORS // bsl::size_t length() const; // // Return a snapshot of the number of elements currently in this // // queue. // // bsl::size_t capacity() const; // // Return the maximum number of elements that this queue can hold. // }; //.. // Next, we define the constructor, which initializes both the index manager // and vector with the supplied capacity: //.. // // CREATORS // IntegerQueue::IntegerQueue(bsl::size_t capacity, // bslma::Allocator *basicAllocator) // : d_indexManager(capacity, basicAllocator) // , d_values(capacity, 0, basicAllocator) // { // } // // IntegerQueue::~IntegerQueue() // { // } //.. // Now, we define 'tryPushBack' and 'tryPopFront', which use the index manager // to reserve an index in the vector, operate on that index, and then commit // that index back to the index manager: //.. // // MANIPULATORS // int IntegerQueue::tryPushBack(int value) // { // unsigned int generation, index; // if (0 == d_indexManager.reservePushIndex(&generation, &index)) { // d_values[index] = value; // d_indexManager.commitPushIndex(generation, index); // return 0; // RETURN // } // return -1; // } // // int IntegerQueue::tryPopFront(int *result) // { // unsigned int generation, index; // if (0 == d_indexManager.reservePopIndex(&generation, &index)) { // *result = d_values[index]; // d_indexManager.commitPopIndex(generation, index); // return 0; // RETURN // } // return -1; // } //.. // Notice that because none of these operations allocate memory, we do not need // to add code to ensure exception safety. // // Then, we define the accessors to the integer queue: //.. // // ACCESSORS // bsl::size_t IntegerQueue::length() const // { // return d_indexManager.length(); // } // // bsl::size_t IntegerQueue::capacity() const // { // return d_indexManager.capacity(); // } //.. // Finally, we create an 'IntegerQueue', and push and pop a couple of elements // into the queue: //.. // IntegerQueue intQueue(2); // int rc = intQueue.tryPushBack(1); // assert(0 == rc); // // rc = intQueue.tryPushBack(2); // assert(0 == rc); // // rc = intQueue.tryPushBack(3); // assert(0 != rc); // // assert(2 == intQueue.length()); // // int result; // // rc = intQueue.tryPopFront(&result); // assert(0 == rc); // assert(1 == result); //.. #include <bdlscm_version.h> #include <bslmt_platform.h> #include <bslma_allocator.h> #include <bslma_usesbslmaallocator.h> #include <bslmf_nestedtraitdeclaration.h> #include <bsls_atomic.h> #include <bsls_platform.h> #include <bsls_performancehint.h> #include <bsl_iosfwd.h> #include <bsl_cstddef.h> #include <bsl_cstdlib.h> namespace BloombergLP { namespace bdlcc { // ============================ // class FixedQueueIndexManager // ============================ class FixedQueueIndexManager { // This class implements a circular buffer of atomic state variables. // These are intended to synchronize access to another (non-atomic) indexed // data structure so that the other data structure can be used as a // thread-enabled fixed-size queue. // PRIVATE CONSTANTS enum { k_PADDING = bslmt::Platform::e_CACHE_LINE_SIZE - sizeof(bsls::AtomicInt) }; // DATA bsls::AtomicInt d_pushIndex; // index in circular buffer in which the next // element will be pushed (see implementation note // in .cpp) const char d_pushIndexPad[k_PADDING]; // padding to prevent false sharing bsls::AtomicInt d_popIndex; // index in the circular buffer from which the next // element will be popped (see implementation note // in .cpp) const char d_popIndexPad[k_PADDING]; // padding to prevent false sharing const bsl::size_t d_capacity; // maximum number of elements that can be held in // the circular buffer const unsigned int d_maxGeneration; // maximum generation count for this object (see // implementation note in the .cpp file for more // detail) const unsigned int d_maxCombinedIndex; // maximum combination of index and generation count // that can stored in 'd_pushIndex' and 'd_popIndex' // of this object (see implementation note in the // .cpp file for more detail) bsls::AtomicInt *d_states; // array of index state variables bslma::Allocator *d_allocator_p; // allocator, held not owned private: // NOT IMPLEMENTED FixedQueueIndexManager(const FixedQueueIndexManager&); // = delete; FixedQueueIndexManager& operator=( const FixedQueueIndexManager&); // = delete; private: // PRIVATE ACCESSORS unsigned int nextCombinedIndex(unsigned int combinedIndex) const; // Return the combined index value subsequent to the specified // 'combinedIndex'. Note that a "combined index" is the combination of // generation count and element index held in 'd_pushIndex' and // 'd_popIndex', and is defined as: //.. // ('generationCount * d_capacity) + index'. //.. // See the implementation note in the .cpp file for more detail. unsigned int nextGeneration(unsigned int generation) const; // Return the generation subsequent to the specified 'generation'. public: // TRAITS BSLMF_NESTED_TRAIT_DECLARATION(FixedQueueIndexManager, bslma::UsesBslmaAllocator); // CLASS METHODS static int circularDifference(unsigned int minuend, unsigned int subtrahend, unsigned int modulo); // Return the difference between the specified 'minuend' and the // specified 'subtrahend' (typically 'minuend - subtrahend') where // minuend and subtrahend are both "circular values", meaning they are // part of a non-euclidean number line where the value wrap around to 0 // at the specified 'modulo'. The difference between two circular // values is the minimum of either the number of increments or the // number of decrements to 'subtrahend' that results in 'minuend' // (i.e., the minimum "distance" between the points on the number // circle), where increments are a positive difference, and decrements // are a negative difference. If the number of increments and number // of decrements between 'minuend' and 'subtrahend' are equal, // 'minuend - subtrahend' is returned. For example, for a hypothetical // compass, [0, 360): //.. // circularDifference( 0, 359, 360) == 1 // circularDifference( 359, 0, 360) == -1 // circularDifference( 180, 0, 360) == 180 // circularDifference( 0, 180, 360) == -180 //.. // The behavior is undefined unless 'minuend < modulo', // 'subtrahend < modulo', and 'modulo <= INT_MAX + 1'. static unsigned int numRepresentableGenerations(bsl::size_t capacity); // Return the number of representable generations for a circular buffer // of the specified 'capacity'. // PUBLIC CONSTANTS enum { k_MAX_CAPACITY = 1 << ((sizeof(int) * 8) - 2) // maximum capacity of an index manager; // note that 2 bits of 'd_pushIndex' are // reserved for holding the disabled status // flag, and ensuring that the // representable number of generation // counts is at least 2 (see the // implementation note in the .cpp for more // details) #ifndef BDE_OMIT_INTERNAL_DEPRECATED // BDE3.0 , e_MAX_CAPACITY = k_MAX_CAPACITY #endif // BDE_OMIT_INTERNAL_DEPRECATED -- BDE3.0 }; // CREATORS explicit FixedQueueIndexManager(bsl::size_t capacity, bslma::Allocator *basicAllocator = 0); // Create an index manager for a circular buffer having the specified // maximum 'capacity'. Optionally specify a 'basicAllocator' used to // supply memory. If 'basicAllocator' is 0, the currently installed // default allocator is used. 'isEnabled' will be 'true' for the newly // created index manager. The behavior is undefined unless // '0 < capacity' and 'capacity <= k_MAX_CAPACITY'. ~FixedQueueIndexManager(); // Destroy this object. // MANIPULATORS // Pushing Elements int reservePushIndex(unsigned int *generation, unsigned int *index); // Reserve the next available index at which to enqueue an element in // an (externally managed) circular buffer; load the specified 'index' // with the reserved index and load the specified 'generation' with the // current generation of the circular buffer. Return 0 on success, a // negative value if the queue is disabled, and a positive value if the // queue is full. If this method succeeds, other threads using this // object may spin on the corresponding index state until // 'commitPushIndex' is called using the returned 'index' and // 'generation' values; clients should call 'commitPushIndex' quickly // after this method returns, without performing any blocking // operations. If this method fails the 'generation' and 'index' will // be unmodified. The behavior is undefined if the current thread is // already holding a reservation on either a push or pop index. Note // that 'generation' is necessary for invoking 'commitPushIndex' but // should not otherwise be used by the caller; the value reflects the // number of times the 'index' in the circular buffer has been used. void commitPushIndex(unsigned int generation, unsigned int index); // Mark the specified 'index' as occupied (full) in the specified // 'generation'. The behavior is undefined unless 'generation' and // 'index' match those returned by a previous successful call to // 'reservePushIndex' (that has not previously been committed). // Popping Elements int reservePopIndex(unsigned int *generation, unsigned int *index); // Reserve the next available index from which to dequeue an element // from an (externally managed) circular buffer; load the specified // 'index' with the reserved index and load the specified 'generation' // with the current generation of the circular buffer. Return 0 on // success, and a non-zero value if the queue is empty. If this method // succeeds, other threads using this object may spin on the // corresponding index state until 'commitPopIndex' is called using the // returned 'index' and 'generation' values; clients should call // 'commitPopIndex' quickly after this method returns, without // performing any blocking operations. If this method fails the // 'generation' and 'index' will be unmodified. The behavior is // undefined if the current thread is already holding a reservation on // either a push or pop index. Note that 'generation' is necessary for // invoking 'commitPopIndex' but should not otherwise be used by the // caller; the value reflects the of times the 'index' in the circular // buffer has been used. void commitPopIndex(unsigned int generation, unsigned int index); // Mark the specified 'index' as available (empty) in the generation // following the specified 'generation'. The behavior is undefined // unless 'generation' and index' match those returned by a previous // successful call to 'reservePopIndex' (that has not previously been // committed). // Disabled State void disable(); // Mark the queue as disabled. Future calls to 'reservePushIndex' will // fail. void enable(); // Mark the queue as enabled. // Exception Safety int reservePopIndexForClear(unsigned int *disposedGeneration, unsigned int *disposedIndex, unsigned int endGeneration, unsigned int endIndex); // If the next available index from which an element can be popped is // before the specified 'endGeneration' and 'endIndex' then reserve // that index for popping and load the specified 'disposedGeneration' // and 'disposedIndex' with the generation and index of the reserved // cell; otherwise this operation has no effect. Return 0 if an index // was successfully reserved, and a non-zero value if the current pop // index is at 'endIndex' and 'endGeneration'. The behavior is // undefined unless 'endGeneration' and 'endIndex' refer to a cell that // has been acquired for writing. Note that this operation is used to // facilitate removing all the elements in a circular buffer if an // exception is thrown between reserving an index for pushing, and // committing that index -- the intended usage is to call // 'reservePopIndexForClear' and then 'commitPopIndex', emptying all // the cells up to the index that was reserved for writing, and then // call 'abortPushIndexReservation' on the reserved index. void abortPushIndexReservation(unsigned int generation, unsigned int index); // Release the specified 'index' and make it available for use in the // generation following the specified 'generation'. The behavior is // undefined unless the calling thread holds a reservation on // 'generation' and 'index', and 'clearPopIndex' and then // 'commitPushIndex' have been repeatedly invoked with 'generation' and // 'index' as input until no indices remain to clear. Note that this // operation is used to facilitate removing all the elements in a // circular buffer if an exception is thrown between reserving an index // for pushing, and committing that index. // ACCESSORS bool isEnabled() const; // Return 'true' if the queue is enabled, and 'false' if it is // disabled. bsl::size_t length() const; // Return a snapshot of the number of items in the queue. bsl::size_t capacity() const; // Return the maximum number of items that may be stored in the queue. bsl::ostream& print(bsl::ostream& stream) const; // Print a formatted string describing the current state of this object // to the specified 'stream'. If 'stream' is not valid on entry, this // operation has no effect. Note that this method describes the // internal state of the buffer and is provided purely for debugging // purposes. }; // ============================================================================ // INLINE DEFINITIONS // ============================================================================ // ---------------------------- // class FixedQueueIndexManager // ---------------------------- // PRIVATE ACCESSORS inline unsigned int FixedQueueIndexManager::nextCombinedIndex( unsigned int combinedIndex) const { if (BSLS_PERFORMANCEHINT_PREDICT_UNLIKELY(d_maxCombinedIndex == combinedIndex)) { // We have reached the maximum representable combination of index and // generation count, so we reset the generation count to 0. return 0; // RETURN } return combinedIndex + 1; } inline unsigned int FixedQueueIndexManager::nextGeneration( unsigned int generation) const { if (BSLS_PERFORMANCEHINT_PREDICT_UNLIKELY(d_maxGeneration == generation)) { return 0; // RETURN } return generation + 1; } // ACCESSORS inline bsl::size_t FixedQueueIndexManager::capacity() const { return d_capacity; } } // close package namespace } // close enterprise namespace #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 ----------------------------------