BDE 4.39.x 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 `combinedIndex`.
314 ///
315 /// \note Note that a "combined index" is the combination of
316 /// generation count and element index held in `d_pushIndex` and
317 /// `d_popIndex`, and is defined as:
318 /// @code
319 /// ('generationCount * d_capacity) + index'.
320 /// @endcode
321 /// See the implementation note in the .cpp file for more detail.
322 unsigned int nextCombinedIndex(unsigned int combinedIndex) const;
323
324 /// Return the generation subsequent to the specified `generation`.
325 unsigned int nextGeneration(unsigned int generation) const;
326
327 public:
328 // TRAITS
331
332 // CLASS METHODS
333
334 /// Return the difference between the specified `minuend` and the
335 /// specified `subtrahend` (typically `minuend - subtrahend`) where
336 /// minuend and subtrahend are both "circular values", meaning they are
337 /// part of a non-euclidean number line where the value wrap around to 0
338 /// at the specified `modulo`. The difference between two circular
339 /// values is the minimum of either the number of increments or the
340 /// number of decrements to `subtrahend` that results in `minuend`
341 /// (i.e., the minimum "distance" between the points on the number
342 /// circle), where increments are a positive difference, and decrements
343 /// are a negative difference. If the number of increments and number
344 /// of decrements between `minuend` and `subtrahend` are equal,
345 /// `minuend - subtrahend` is returned. For example, for a hypothetical
346 /// compass, [0, 360):
347 /// @code
348 /// circularDifference( 0, 359, 360) == 1
349 /// circularDifference( 359, 0, 360) == -1
350 /// circularDifference( 180, 0, 360) == 180
351 /// circularDifference( 0, 180, 360) == -180
352 /// @endcode
353 ///
354 /// \pre The behavior is undefined unless `minuend < modulo`,
355 /// `subtrahend < modulo`, and `modulo <= INT_MAX + 1`.
356 static int circularDifference(unsigned int minuend,
357 unsigned int subtrahend,
358 unsigned int modulo);
359
360 /// Return the number of representable generations for a circular buffer
361 /// of the specified `capacity`.
362 static unsigned int numRepresentableGenerations(bsl::size_t capacity);
363
364 // PUBLIC CONSTANTS
365 enum {
366 k_MAX_CAPACITY = 1 << ((sizeof(int) * 8) - 2)
367 // maximum capacity of an index manager;
368 // note that 2 bits of `d_pushIndex` are
369 // reserved for holding the disabled status
370 // flag, and ensuring that the
371 // representable number of generation
372 // counts is at least 2 (see the
373 // implementation note in the .cpp for more
374 // details)
375
376#ifndef BDE_OMIT_INTERNAL_DEPRECATED
378#endif // BDE_OMIT_INTERNAL_DEPRECATED
379
380 };
381
382 // CREATORS
383
384 /// Create an index manager for a circular buffer having the specified
385 /// maximum `capacity`. Optionally specify a `basicAllocator` used to
386 /// supply memory. If `basicAllocator` is 0, the currently installed
387 /// default allocator is used. `isEnabled` will be `true` for the newly created index manager.
388 ///
389 /// \pre The behavior is undefined unless
390 /// `0 < capacity` and `capacity <= k_MAX_CAPACITY`.
391 explicit
393 bslma::Allocator *basicAllocator = 0);
394
395 /// Destroy this object.
397
398 // MANIPULATORS
399 // Pushing Elements
400
401 /// Reserve the next available index at which to enqueue an element in
402 /// an (externally managed) circular buffer; load the specified `index`
403 /// with the reserved index and load the specified `generation` with the
404 /// current generation of the circular buffer. Return 0 on success, a
405 /// negative value if the queue is disabled, and a positive value if the
406 /// queue is full. If this method succeeds, other threads using this
407 /// object may spin on the corresponding index state until
408 /// `commitPushIndex` is called using the returned `index` and
409 /// `generation` values; clients should call `commitPushIndex` quickly
410 /// after this method returns, without performing any blocking
411 /// operations. If this method fails the `generation` and `index` will be unmodified.
412 ///
413 /// \pre The behavior is undefined if the current thread is
414 /// already holding a reservation on either a push or pop index.
415 ///
416 /// \note Note that `generation` is necessary for invoking `commitPushIndex` but
417 /// should not otherwise be used by the caller; the value reflects the
418 /// number of times the `index` in the circular buffer has been used.
419 int reservePushIndex(unsigned int *generation, unsigned int *index);
420
421 /// Mark the specified `index` as occupied (full) in the specified `generation`.
422 ///
423 /// \pre The behavior is undefined unless `generation` and
424 /// `index` match those returned by a previous successful call to
425 /// `reservePushIndex` (that has not previously been committed).
426 void commitPushIndex(unsigned int generation, unsigned int index);
427
428 // Popping Elements
429
430 /// Reserve the next available index from which to dequeue an element
431 /// from an (externally managed) circular buffer; load the specified
432 /// `index` with the reserved index and load the specified `generation`
433 /// with the current generation of the circular buffer. Return 0 on
434 /// success, and a non-zero value if the queue is empty. If this method
435 /// succeeds, other threads using this object may spin on the
436 /// corresponding index state until `commitPopIndex` is called using the
437 /// returned `index` and `generation` values; clients should call
438 /// `commitPopIndex` quickly after this method returns, without
439 /// performing any blocking operations. If this method fails the
440 /// `generation` and `index` will be unmodified.
441 ///
442 /// \pre The behavior is undefined if the current thread is already holding a reservation on either a push or pop index.
443 ///
444 /// \note Note that `generation` is necessary for
445 /// invoking `commitPopIndex` but should not otherwise be used by the
446 /// caller; the value reflects the of times the `index` in the circular
447 /// buffer has been used.
448 int reservePopIndex(unsigned int *generation, unsigned int *index);
449
450 /// Mark the specified `index` as available (empty) in the generation
451 /// following the specified `generation`.
452 ///
453 /// \pre The behavior is undefined unless `generation` and index' match those returned by a previous
454 /// successful call to `reservePopIndex` (that has not previously been
455 /// committed).
456 void commitPopIndex(unsigned int generation, unsigned int index);
457
458 // Disabled State
459
460 /// Mark the queue as disabled. Future calls to `reservePushIndex` will
461 /// fail.
462 void disable();
463
464 /// Mark the queue as enabled.
465 void enable();
466
467 // Exception Safety
468
469 /// If the next available index from which an element can be popped is
470 /// before the specified `endGeneration` and `endIndex` then reserve
471 /// that index for popping and load the specified `disposedGeneration`
472 /// and `disposedIndex` with the generation and index of the reserved
473 /// cell; otherwise this operation has no effect. Return 0 if an index
474 /// was successfully reserved, and a non-zero value if the current pop
475 /// index is at `endIndex` and `endGeneration`.
476 ///
477 /// \pre The behavior is undefined unless `endGeneration` and `endIndex` refer to a cell that has been acquired for writing.
478 ///
479 /// \note Note that this operation is used to
480 /// facilitate removing all the elements in a circular buffer if an
481 /// exception is thrown between reserving an index for pushing, and
482 /// committing that index -- the intended usage is to call
483 /// `reservePopIndexForClear` and then `commitPopIndex`, emptying all
484 /// the cells up to the index that was reserved for writing, and then
485 /// call `abortPushIndexReservation` on the reserved index.
486 int reservePopIndexForClear(unsigned int *disposedGeneration,
487 unsigned int *disposedIndex,
488 unsigned int endGeneration,
489 unsigned int endIndex);
490
491 /// Release the specified `index` and make it available for use in the
492 /// generation following the specified `generation`.
493 ///
494 /// \pre The behavior is undefined unless the calling thread holds a reservation on
495 /// `generation` and `index`, and `clearPopIndex` and then
496 /// `commitPushIndex` have been repeatedly invoked with `generation` and `index` as input until no indices remain to clear.
497 ///
498 /// \note Note that this
499 /// operation is used to facilitate removing all the elements in a
500 /// circular buffer if an exception is thrown between reserving an index
501 /// for pushing, and committing that index.
502 void abortPushIndexReservation(unsigned int generation,
503 unsigned int index);
504
505 // ACCESSORS
506
507 /// Return `true` if the queue is enabled, and `false` if it is
508 /// disabled.
509 bool isEnabled() const;
510
511 /// Return a snapshot of the number of items in the queue.
512 bsl::size_t length() const;
513
514 /// Return the maximum number of items that may be stored in the queue.
515 bsl::size_t capacity() const;
516
517 /// Print a formatted string describing the current state of this object
518 /// to the specified `stream`. If `stream` is not valid on entry, this operation has no effect.
519 ///
520 /// \note Note that this method describes the
521 /// internal state of the buffer and is provided purely for debugging
522 /// purposes.
523 bsl::ostream& print(bsl::ostream& stream) const;
524};
525
526// ============================================================================
527// INLINE DEFINITIONS
528// ============================================================================
529
530 // ----------------------------
531 // class FixedQueueIndexManager
532 // ----------------------------
533
534// PRIVATE ACCESSORS
535inline
536unsigned int FixedQueueIndexManager::nextCombinedIndex(
537 unsigned int combinedIndex) const
538{
539 if (BSLS_PERFORMANCEHINT_PREDICT_UNLIKELY(d_maxCombinedIndex ==
540 combinedIndex)) {
541 // We have reached the maximum representable combination of index and
542 // generation count, so we reset the generation count to 0.
543
544 return 0; // RETURN
545 }
546
547 return combinedIndex + 1;
548
549}
550
551inline
552unsigned int FixedQueueIndexManager::nextGeneration(
553 unsigned int generation) const
554{
555 if (BSLS_PERFORMANCEHINT_PREDICT_UNLIKELY(d_maxGeneration == generation)) {
556 return 0; // RETURN
557 }
558 return generation + 1;
559}
560
561// ACCESSORS
562inline
564{
565 return d_capacity;
566}
567
568} // close package namespace
569
570
571#endif
572
573// ----------------------------------------------------------------------------
574// Copyright 2015 Bloomberg Finance L.P.
575//
576// Licensed under the Apache License, Version 2.0 (the "License");
577// you may not use this file except in compliance with the License.
578// You may obtain a copy of the License at
579//
580// http://www.apache.org/licenses/LICENSE-2.0
581//
582// Unless required by applicable law or agreed to in writing, software
583// distributed under the License is distributed on an "AS IS" BASIS,
584// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
585// See the License for the specific language governing permissions and
586// limitations under the License.
587// ----------------------------- END-OF-FILE ----------------------------------
588
589/** @} */
590/** @} */
591/** @} */
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:563
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:377
@ k_MAX_CAPACITY
Definition bdlcc_fixedqueueindexmanager.h:366
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:545
Definition bsls_atomic.h:744
#define BSLS_IDENT(str)
BSLS_IDENT() - insert string into .comment binary segment (if supported)
Definition bsls_ident.h:238
#define BSLS_PERFORMANCEHINT_PREDICT_UNLIKELY(expr)
Definition bsls_performancehint.h:452
Definition bdlcc_boundedqueue.h:270
Definition bslma_usesbslmaallocator.h:344
@ e_CACHE_LINE_SIZE
Definition bslmt_platform.h:214