BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bdlcc_fixedqueueindexmanager.h
Go to the documentation of this file.
1/// @file bdlcc_fixedqueueindexmanager.h
2///
3/// The content of this file has been pre-processed for Doxygen.
4///
5
6
7// bdlcc_fixedqueueindexmanager.h -*-C++-*-
8#ifndef INCLUDED_BDLCC_FIXEDQUEUEINDEXMANAGER
9#define INCLUDED_BDLCC_FIXEDQUEUEINDEXMANAGER
10
11#include <bsls_ident.h>
12BSLS_IDENT("$Id: $")
13
14/// @defgroup bdlcc_fixedqueueindexmanager bdlcc_fixedqueueindexmanager
15/// @brief Provide thread-enabled state management for a fixed-size queue.
16/// @addtogroup bdl
17/// @{
18/// @addtogroup bdlcc
19/// @{
20/// @addtogroup bdlcc_fixedqueueindexmanager
21/// @{
22///
23/// <h1> Outline </h1>
24/// * <a href="#bdlcc_fixedqueueindexmanager-purpose"> Purpose</a>
25/// * <a href="#bdlcc_fixedqueueindexmanager-classes"> Classes </a>
26/// * <a href="#bdlcc_fixedqueueindexmanager-description"> Description </a>
27/// * <a href="#bdlcc_fixedqueueindexmanager-thread-safety"> Thread Safety </a>
28/// * <a href="#bdlcc_fixedqueueindexmanager-exception-safety"> Exception safety </a>
29/// * <a href="#bdlcc_fixedqueueindexmanager-usage"> Usage </a>
30/// * <a href="#bdlcc_fixedqueueindexmanager-example-1-creating-a-thread-safe-queue-of-integers"> Example 1: Creating a Thread-Safe Queue of Integers </a>
31///
32/// # Purpose {#bdlcc_fixedqueueindexmanager-purpose}
33/// Provide thread-enabled state management for a fixed-size queue.
34///
35/// # Classes {#bdlcc_fixedqueueindexmanager-classes}
36///
37/// - bdlcc::FixedQueueIndexManager: state management for a queue
38///
39/// # Description {#bdlcc_fixedqueueindexmanager-description}
40/// This component implements a lock-free mechanism for managing
41/// the indices of a circular buffer of elements to facilitate the
42/// implementation of a fixed-size thread-enabled single-ended queue. A
43/// `bdlcc::FixedQueueIndexManager` is supplied the size of a circular buffer on
44/// construction, and provides the methods to reserve indices for enqueing and
45/// dequeing elements in that buffer. The actual buffer is held in some other
46/// (external) data structure managed by the user of this component.
47///
48/// This component is not *itself* a general-purpose queue data structure. For
49/// example, no user data of any kind is stored in this data structure (it is
50/// not a queue of integers), and successful invocation of certain methods
51/// (`reservePopIndex`, `reservePushIndex`) obligates the caller to invoke a
52/// corresponding method (`commitPopIndex`, `commitPushIndex` respectively);
53/// otherwise, other threads may "spin" indefinitely with severe performance
54/// consequences.
55///
56/// ## Thread Safety {#bdlcc_fixedqueueindexmanager-thread-safety}
57///
58///
59/// `bdlcc::FixedQueueIndexManager` is fully *thread-safe*, meaning that all
60/// non-creator operations on an object can be safely invoked simultaneously
61/// from multiple threads.
62///
63/// ## Exception safety {#bdlcc_fixedqueueindexmanager-exception-safety}
64///
65///
66/// All methods of the `bdlcc::FixedQueueIndexManager` provide a no-throw
67/// exception guarantee, except for the constructor, which is exception neutral.
68///
69/// ## Usage {#bdlcc_fixedqueueindexmanager-usage}
70///
71///
72/// This section illustrates intended use of this component.
73///
74/// ### Example 1: Creating a Thread-Safe Queue of Integers {#bdlcc_fixedqueueindexmanager-example-1-creating-a-thread-safe-queue-of-integers}
75///
76///
77/// In the following example we create a simple thread-safe queue of integers
78/// using a `bdlcc::FixedQueueIndexManager` to synchronize the queue operations.
79///
80/// We start by declaring the data members of an `IntegerQueue`, a vector of
81/// integers, to hold the values in the queue, and an index manager to ensure
82/// thread-safe access to the indices of the vector:
83/// @code
84/// /// This class provides a fully thread-safe queue of integers with a
85/// /// fixed maximum capacity.
86/// class IntegerQueue {
87///
88/// // DATA
89/// bdlcc::FixedQueueIndexManager d_indexManager; // manages `d_values`
90/// // indices
91///
92/// bsl::vector<int> d_values; // maintains values
93///
94/// private:
95/// // Not implemented:
96/// IntegerQueue(const IntegerQueue&);
97///
98/// public:
99/// @endcode
100/// Then, we declare the methods of an integer queue:
101/// @code
102/// // CREATORS
103///
104/// /// Create a queue capable of holding up to the specified
105/// /// `capacity` number of integer values.
106/// explicit IntegerQueue(bsl::size_t capacity,
107/// bslma::Allocator *basicAllocator = 0);
108///
109/// /// Destroy this queue.
110/// ~IntegerQueue();
111///
112/// // MANIPULATORS
113///
114/// /// Attempt to push the specified `value` onto the back of this
115/// /// queue. Return 0 on success, and a non-zero value if this queue
116/// /// is full.
117/// int tryPushBack(int value);
118///
119/// /// Attempt to remove an element from the front of this queue and
120/// /// load the removed value into the specified `result`. Return 0
121/// /// on success, and a non-zero value otherwise.
122/// int tryPopFront(int *result);
123///
124/// // ACCESSORS
125///
126/// /// Return a snapshot of the number of elements currently in this
127/// /// queue.
128/// bsl::size_t length() const;
129///
130/// /// Return the maximum number of elements that this queue can hold.
131/// bsl::size_t capacity() const;
132/// };
133/// @endcode
134/// Next, we define the constructor, which initializes both the index manager
135/// and vector with the supplied capacity:
136/// @code
137/// // CREATORS
138/// IntegerQueue::IntegerQueue(bsl::size_t capacity,
139/// bslma::Allocator *basicAllocator)
140/// : d_indexManager(capacity, basicAllocator)
141/// , d_values(capacity, 0, basicAllocator)
142/// {
143/// }
144///
145/// IntegerQueue::~IntegerQueue()
146/// {
147/// }
148/// @endcode
149/// Now, we define `tryPushBack` and `tryPopFront`, which use the index manager
150/// to reserve an index in the vector, operate on that index, and then commit
151/// that index back to the index manager:
152/// @code
153/// // MANIPULATORS
154/// int IntegerQueue::tryPushBack(int value)
155/// {
156/// unsigned int generation, index;
157/// if (0 == d_indexManager.reservePushIndex(&generation, &index)) {
158/// d_values[index] = value;
159/// d_indexManager.commitPushIndex(generation, index);
160/// return 0; // RETURN
161/// }
162/// return -1;
163/// }
164///
165/// int IntegerQueue::tryPopFront(int *result)
166/// {
167/// unsigned int generation, index;
168/// if (0 == d_indexManager.reservePopIndex(&generation, &index)) {
169/// *result = d_values[index];
170/// d_indexManager.commitPopIndex(generation, index);
171/// return 0; // RETURN
172/// }
173/// return -1;
174/// }
175/// @endcode
176/// Notice that because none of these operations allocate memory, we do not need
177/// to add code to ensure exception safety.
178///
179/// Then, we define the accessors to the integer queue:
180/// @code
181/// // ACCESSORS
182/// bsl::size_t IntegerQueue::length() const
183/// {
184/// return d_indexManager.length();
185/// }
186///
187/// bsl::size_t IntegerQueue::capacity() const
188/// {
189/// return d_indexManager.capacity();
190/// }
191/// @endcode
192/// Finally, we create an `IntegerQueue`, and push and pop a couple of elements
193/// into the queue:
194/// @code
195/// IntegerQueue intQueue(2);
196/// int rc = intQueue.tryPushBack(1);
197/// assert(0 == rc);
198///
199/// rc = intQueue.tryPushBack(2);
200/// assert(0 == rc);
201///
202/// rc = intQueue.tryPushBack(3);
203/// assert(0 != rc);
204///
205/// assert(2 == intQueue.length());
206///
207/// int result;
208///
209/// rc = intQueue.tryPopFront(&result);
210/// assert(0 == rc);
211/// assert(1 == result);
212/// @endcode
213/// @}
214/** @} */
215/** @} */
216
217/** @addtogroup bdl
218 * @{
219 */
220/** @addtogroup bdlcc
221 * @{
222 */
223/** @addtogroup bdlcc_fixedqueueindexmanager
224 * @{
225 */
226
227#include <bdlscm_version.h>
228
229#include <bslmt_platform.h>
230
231#include <bslma_allocator.h>
233
235
236#include <bsls_atomic.h>
237#include <bsls_platform.h>
238#include <bsls_performancehint.h>
239
240#include <bsl_iosfwd.h>
241#include <bsl_cstddef.h>
242#include <bsl_cstdlib.h>
243
244
245namespace bdlcc {
246
247 // ============================
248 // class FixedQueueIndexManager
249 // ============================
250
251/// This class implements a circular buffer of atomic state variables. These
252/// are intended to synchronize access to another (non-atomic) indexed data
253/// structure so that the other data structure can be used as a thread-enabled
254/// fixed-size queue.
255///
256/// See @ref bdlcc_fixedqueueindexmanager
258
259 // PRIVATE CONSTANTS
260 enum {
261 k_PADDING =
263 };
264
265 // DATA
266 bsls::AtomicInt d_pushIndex;
267 // index in circular buffer in which the next
268 // element will be pushed (see implementation note
269 // in .cpp)
270
271 const char d_pushIndexPad[k_PADDING];
272 // padding to prevent false sharing
273
274 bsls::AtomicInt d_popIndex;
275 // index in the circular buffer from which the next
276 // element will be popped (see implementation note
277 // in .cpp)
278
279 const char d_popIndexPad[k_PADDING];
280 // padding to prevent false sharing
281
282 const bsl::size_t d_capacity;
283 // maximum number of elements that can be held in
284 // the circular buffer
285
286 const unsigned int d_maxGeneration;
287 // maximum generation count for this object (see
288 // implementation note in the .cpp file for more
289 // detail)
290
291 const unsigned int d_maxCombinedIndex;
292 // maximum combination of index and generation count
293 // that can stored in `d_pushIndex` and `d_popIndex`
294 // of this object (see implementation note in the
295 // .cpp file for more detail)
296
297 bsls::AtomicInt *d_states;
298 // array of index state variables
299
300 bslma::Allocator *d_allocator_p;
301 // allocator, held not owned
302
303 private:
304 // NOT IMPLEMENTED
306 FixedQueueIndexManager& operator=(
307 const FixedQueueIndexManager&); // = delete;
308
309 private:
310
311 // PRIVATE ACCESSORS
312
313 /// Return the combined index value subsequent to the specified
314 /// `combinedIndex`. Note that a "combined index" is the combination of
315 /// generation count and element index held in `d_pushIndex` and
316 /// `d_popIndex`, and is defined as:
317 /// @code
318 /// ('generationCount * d_capacity) + index'.
319 /// @endcode
320 /// See the implementation note in the .cpp file for more detail.
321 unsigned int nextCombinedIndex(unsigned int combinedIndex) const;
322
323 /// Return the generation subsequent to the specified `generation`.
324 unsigned int nextGeneration(unsigned int generation) const;
325
326 public:
327 // TRAITS
330
331 // CLASS METHODS
332
333 /// Return the difference between the specified `minuend` and the
334 /// specified `subtrahend` (typically `minuend - subtrahend`) where
335 /// minuend and subtrahend are both "circular values", meaning they are
336 /// part of a non-euclidean number line where the value wrap around to 0
337 /// at the specified `modulo`. The difference between two circular
338 /// values is the minimum of either the number of increments or the
339 /// number of decrements to `subtrahend` that results in `minuend`
340 /// (i.e., the minimum "distance" between the points on the number
341 /// circle), where increments are a positive difference, and decrements
342 /// are a negative difference. If the number of increments and number
343 /// of decrements between `minuend` and `subtrahend` are equal,
344 /// `minuend - subtrahend` is returned. For example, for a hypothetical
345 /// compass, [0, 360):
346 /// @code
347 /// circularDifference( 0, 359, 360) == 1
348 /// circularDifference( 359, 0, 360) == -1
349 /// circularDifference( 180, 0, 360) == 180
350 /// circularDifference( 0, 180, 360) == -180
351 /// @endcode
352 /// The behavior is undefined unless `minuend < modulo`,
353 /// `subtrahend < modulo`, and `modulo <= INT_MAX + 1`.
354 static int circularDifference(unsigned int minuend,
355 unsigned int subtrahend,
356 unsigned int modulo);
357
358 /// Return the number of representable generations for a circular buffer
359 /// of the specified `capacity`.
360 static unsigned int numRepresentableGenerations(bsl::size_t capacity);
361
362 // PUBLIC CONSTANTS
363 enum {
364 k_MAX_CAPACITY = 1 << ((sizeof(int) * 8) - 2)
365 // maximum capacity of an index manager;
366 // note that 2 bits of `d_pushIndex` are
367 // reserved for holding the disabled status
368 // flag, and ensuring that the
369 // representable number of generation
370 // counts is at least 2 (see the
371 // implementation note in the .cpp for more
372 // details)
373
374#ifndef BDE_OMIT_INTERNAL_DEPRECATED
376#endif // BDE_OMIT_INTERNAL_DEPRECATED
377
378 };
379
380 // CREATORS
381
382 /// Create an index manager for a circular buffer having the specified
383 /// maximum `capacity`. Optionally specify a `basicAllocator` used to
384 /// supply memory. If `basicAllocator` is 0, the currently installed
385 /// default allocator is used. `isEnabled` will be `true` for the newly
386 /// created index manager. The behavior is undefined unless
387 /// `0 < capacity` and `capacity <= k_MAX_CAPACITY`.
388 explicit
390 bslma::Allocator *basicAllocator = 0);
391
392 /// Destroy this object.
394
395 // MANIPULATORS
396 // Pushing Elements
397
398 /// Reserve the next available index at which to enqueue an element in
399 /// an (externally managed) circular buffer; load the specified `index`
400 /// with the reserved index and load the specified `generation` with the
401 /// current generation of the circular buffer. Return 0 on success, a
402 /// negative value if the queue is disabled, and a positive value if the
403 /// queue is full. If this method succeeds, other threads using this
404 /// object may spin on the corresponding index state until
405 /// `commitPushIndex` is called using the returned `index` and
406 /// `generation` values; clients should call `commitPushIndex` quickly
407 /// after this method returns, without performing any blocking
408 /// operations. If this method fails the `generation` and `index` will
409 /// be unmodified. The behavior is undefined if the current thread is
410 /// already holding a reservation on either a push or pop index. Note
411 /// that `generation` is necessary for invoking `commitPushIndex` but
412 /// should not otherwise be used by the caller; the value reflects the
413 /// number of times the `index` in the circular buffer has been used.
414 int reservePushIndex(unsigned int *generation, unsigned int *index);
415
416 /// Mark the specified `index` as occupied (full) in the specified
417 /// `generation`. The behavior is undefined unless `generation` and
418 /// `index` match those returned by a previous successful call to
419 /// `reservePushIndex` (that has not previously been committed).
420 void commitPushIndex(unsigned int generation, unsigned int index);
421
422 // Popping Elements
423
424 /// Reserve the next available index from which to dequeue an element
425 /// from an (externally managed) circular buffer; load the specified
426 /// `index` with the reserved index and load the specified `generation`
427 /// with the current generation of the circular buffer. Return 0 on
428 /// success, and a non-zero value if the queue is empty. If this method
429 /// succeeds, other threads using this object may spin on the
430 /// corresponding index state until `commitPopIndex` is called using the
431 /// returned `index` and `generation` values; clients should call
432 /// `commitPopIndex` quickly after this method returns, without
433 /// performing any blocking operations. If this method fails the
434 /// `generation` and `index` will be unmodified. The behavior is
435 /// undefined if the current thread is already holding a reservation on
436 /// either a push or pop index. Note that `generation` is necessary for
437 /// invoking `commitPopIndex` but should not otherwise be used by the
438 /// caller; the value reflects the of times the `index` in the circular
439 /// buffer has been used.
440 int reservePopIndex(unsigned int *generation, unsigned int *index);
441
442 /// Mark the specified `index` as available (empty) in the generation
443 /// following the specified `generation`. The behavior is undefined
444 /// unless `generation` and index' match those returned by a previous
445 /// successful call to `reservePopIndex` (that has not previously been
446 /// committed).
447 void commitPopIndex(unsigned int generation, unsigned int index);
448
449 // Disabled State
450
451 /// Mark the queue as disabled. Future calls to `reservePushIndex` will
452 /// fail.
453 void disable();
454
455 /// Mark the queue as enabled.
456 void enable();
457
458 // Exception Safety
459
460 /// If the next available index from which an element can be popped is
461 /// before the specified `endGeneration` and `endIndex` then reserve
462 /// that index for popping and load the specified `disposedGeneration`
463 /// and `disposedIndex` with the generation and index of the reserved
464 /// cell; otherwise this operation has no effect. Return 0 if an index
465 /// was successfully reserved, and a non-zero value if the current pop
466 /// index is at `endIndex` and `endGeneration`. The behavior is
467 /// undefined unless `endGeneration` and `endIndex` refer to a cell that
468 /// has been acquired for writing. Note that this operation is used to
469 /// facilitate removing all the elements in a circular buffer if an
470 /// exception is thrown between reserving an index for pushing, and
471 /// committing that index -- the intended usage is to call
472 /// `reservePopIndexForClear` and then `commitPopIndex`, emptying all
473 /// the cells up to the index that was reserved for writing, and then
474 /// call `abortPushIndexReservation` on the reserved index.
475 int reservePopIndexForClear(unsigned int *disposedGeneration,
476 unsigned int *disposedIndex,
477 unsigned int endGeneration,
478 unsigned int endIndex);
479
480 /// Release the specified `index` and make it available for use in the
481 /// generation following the specified `generation`. The behavior is
482 /// undefined unless the calling thread holds a reservation on
483 /// `generation` and `index`, and `clearPopIndex` and then
484 /// `commitPushIndex` have been repeatedly invoked with `generation` and
485 /// `index` as input until no indices remain to clear. Note that this
486 /// operation is used to facilitate removing all the elements in a
487 /// circular buffer if an exception is thrown between reserving an index
488 /// for pushing, and committing that index.
489 void abortPushIndexReservation(unsigned int generation,
490 unsigned int index);
491
492 // ACCESSORS
493
494 /// Return `true` if the queue is enabled, and `false` if it is
495 /// disabled.
496 bool isEnabled() const;
497
498 /// Return a snapshot of the number of items in the queue.
499 bsl::size_t length() const;
500
501 /// Return the maximum number of items that may be stored in the queue.
502 bsl::size_t capacity() const;
503
504 /// Print a formatted string describing the current state of this object
505 /// to the specified `stream`. If `stream` is not valid on entry, this
506 /// operation has no effect. Note that this method describes the
507 /// internal state of the buffer and is provided purely for debugging
508 /// purposes.
509 bsl::ostream& print(bsl::ostream& stream) const;
510};
511
512// ============================================================================
513// INLINE DEFINITIONS
514// ============================================================================
515
516 // ----------------------------
517 // class FixedQueueIndexManager
518 // ----------------------------
519
520// PRIVATE ACCESSORS
521inline
522unsigned int FixedQueueIndexManager::nextCombinedIndex(
523 unsigned int combinedIndex) const
524{
525 if (BSLS_PERFORMANCEHINT_PREDICT_UNLIKELY(d_maxCombinedIndex ==
526 combinedIndex)) {
527 // We have reached the maximum representable combination of index and
528 // generation count, so we reset the generation count to 0.
529
530 return 0; // RETURN
531 }
532
533 return combinedIndex + 1;
534
535}
536
537inline
538unsigned int FixedQueueIndexManager::nextGeneration(
539 unsigned int generation) const
540{
541 if (BSLS_PERFORMANCEHINT_PREDICT_UNLIKELY(d_maxGeneration == generation)) {
542 return 0; // RETURN
543 }
544 return generation + 1;
545}
546
547// ACCESSORS
548inline
550{
551 return d_capacity;
552}
553
554} // close package namespace
555
556
557#endif
558
559// ----------------------------------------------------------------------------
560// Copyright 2015 Bloomberg Finance L.P.
561//
562// Licensed under the Apache License, Version 2.0 (the "License");
563// you may not use this file except in compliance with the License.
564// You may obtain a copy of the License at
565//
566// http://www.apache.org/licenses/LICENSE-2.0
567//
568// Unless required by applicable law or agreed to in writing, software
569// distributed under the License is distributed on an "AS IS" BASIS,
570// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
571// See the License for the specific language governing permissions and
572// limitations under the License.
573// ----------------------------- END-OF-FILE ----------------------------------
574
575/** @} */
576/** @} */
577/** @} */
Definition bdlcc_fixedqueueindexmanager.h:257
~FixedQueueIndexManager()
Destroy this object.
FixedQueueIndexManager(bsl::size_t capacity, bslma::Allocator *basicAllocator=0)
void commitPushIndex(unsigned int generation, unsigned int index)
bsl::size_t capacity() const
Return the maximum number of items that may be stored in the queue.
Definition bdlcc_fixedqueueindexmanager.h:549
int reservePopIndex(unsigned int *generation, unsigned int *index)
static int circularDifference(unsigned int minuend, unsigned int subtrahend, unsigned int modulo)
int reservePushIndex(unsigned int *generation, unsigned int *index)
@ e_MAX_CAPACITY
Definition bdlcc_fixedqueueindexmanager.h:375
@ k_MAX_CAPACITY
Definition bdlcc_fixedqueueindexmanager.h:364
void enable()
Mark the queue as enabled.
static unsigned int numRepresentableGenerations(bsl::size_t capacity)
void abortPushIndexReservation(unsigned int generation, unsigned int index)
void commitPopIndex(unsigned int generation, unsigned int index)
bsl::size_t length() const
Return a snapshot of the number of items in the queue.
BSLMF_NESTED_TRAIT_DECLARATION(FixedQueueIndexManager, bslma::UsesBslmaAllocator)
bsl::ostream & print(bsl::ostream &stream) const
int reservePopIndexForClear(unsigned int *disposedGeneration, unsigned int *disposedIndex, unsigned int endGeneration, unsigned int endIndex)
Definition bslma_allocator.h:457
Definition bsls_atomic.h:743
#define BSLS_IDENT(str)
Definition bsls_ident.h:195
#define BSLS_PERFORMANCEHINT_PREDICT_UNLIKELY(expr)
Definition bsls_performancehint.h:452
Definition bdlcc_boundedqueue.h:270
Definition bslma_usesbslmaallocator.h:343
@ e_CACHE_LINE_SIZE
Definition bslmt_platform.h:213