BDE 4.14.0 Production release
|
Provide a fully thread-safe deque container.
bsl::deque
wrapperThis component provides bdlcc::Deque<TYPE>
, a fully thread-safe implementation of an efficient, double-ended queue of (template parameter) TYPE
values. bdlcc::Deque
is effectively a thread-safe wrapper for bsl::deque
, whose interface is also made available through proctor types that are nested classes.
bdlcc::Deque
is fully thread-safe, meaning that all non-creator operations on an object can be safely invoked simultaneously from multiple threads.
Provided the template parameter TYPE
provides the following exception safety guarantees:
bdlcc::Deque
provide the strong exception guarantee, both for the bdlcc::Deque
s own salient state and the salient state of the vector
, if any, passed to manipulators. However, the non-salient capacity
of the underlying bsl::deque
and of the passed vector
may be modified.The fully thread-safe bdlcc::Deque
is similar to bsl::deque
in many regards, but there are several differences in method behavior and signature that arise due to the thread-aware nature of the container and its anticipated usage pattern.
A user of bsl::deque
is expected to consult the size
or empty
accessors before reading or popping to determine whether elements are available in the container to be read or popped. This won't work in a multithreaded context since reading the accessor is a separate operation than the read or pop, and another thread may have altered the state of the container in between.
So we have eliminated the front
, back
and random-access methods. Reading is done from the ends of the container via the popFront
and popBack
methods, which return a TYPE
object by value, rather than returning void
, as pop_front and pop_back in bsl::deque
do. Moreover, if a bdlcc::Deque
object is empty, popFront
and popBack
will block indefinitely until an item is added to the container.
The behaviors of the push
methods differ from those of bsl::deque
in that they can block under certain circumstances. bdlcc::Deque
supports the notion of a suggested maximum capacity known as the high-water mark. The high-water mark value is supplied at construction, and affects some of the various forms of push*
methods. The container is considered to be full if it contains (at least) the high-water mark number of items, and the container has space available if it is not full. The high-water mark is set at construction and cannot be changed afterward. If no high-water mark is specified, the high-water mark of the container is effectively inifinite. Some of the variants of push operations (described below) may fail, and the return status of those operations indicates whether the operation succeeded, failed, or partially succeeded (which may happen, for example, when pushing a range of values).
bdlcc::Deque
supports four variants of the two push
methods, whose behaviors differ when the container is full (i.e. when the push would raise the length of the container above the high-water mark).
pushBack
, pushFront
): If the container is full, block until space is available, then push, otherwise push immediately.tryPushBack
, tryPushFront
): If the container is full, fail immediately. If space is available, succeed immediately. Note that partial success is possible in the case of a range try push.timedPushBack
, timedPushFront
): If the container is full, block until either space is available or the specified timeout has been reached. If space was, or became, available, push and succeed, otherwise fail.forcePushBack
, forcePushFront
): If the container is full, push anyway, increasing the container's size above its high-water mark, always succeeding immediately.Note that the availability of force pushes means that the high-water mark is a suggestion and not an invariant.
The purpose of a high-water mark is to enable the client to use the container as a fixed-length container, where pushes that will grow it above a certain size will block. The purpose of the force pushes is to allow high-priority items to be pushed regardless of whether the container is full.
There are public nested classes bdlcc::Deque::Proctor
and bdlcc::Deque::ConstProctor
through which the client can directly access the underlying bsl::deque
contained in the bdlcc::Deque
. When a proctor object is created, it acquires the container's mutex, and allows the client to use the overloaded ->
and *
operators on the proctor object to access the underlying bsl::deque
. operator[]
is also provided for direct random access to that deque. Because the mutex is locked, manipulators of bdlcc::Deque
called by other threads will block, thus allowing safe access to the underlying thread-unsafe container. When the proctor is destroyed (or released via the release
method), the proctor signals the thread-aware container's condition variables to inform manipulators in other threads of new items being available for pops or new space becoming available for pushes.
The component bsls::SystemClockType
supplies the enumeration indicating the system clock on which the timedPush*
and timedPop*
methods should be based. If the clock type indicated at construction is bsls::SystemClockType::e_REALTIME
, time should be expressed as an absolute offset since 00:00:00 UTC, January 1, 1970 (which matches the epoch used in bdlt::SystemTime::now(bsls::SystemClockType::e_REALTIME)
. If the clock type indicated at construction is bsls::SystemClockType::e_MONOTONIC
, time should be expressed as an absolute offset since the epoch of this clock (which matches the epoch used in bsls::SystemTime::now(bsls::SystemClockType::e_MONOTONIC)
.
The behavior for the destructor is undefined unless all access or modification of the object is completed prior to its destruction. Some form of synchronization, external to the component, is required to ensure this precondition on the destructor is met. For example, if two (or more) threads are manipulating a queue, it is not safe to anticipate the number of elements added to the queue, and destroy that queue immediately after the last element is popped (without additional synchronization) because one of the corresponding push functions may not have completed (push may, for instance, signal waiting threads after the element is considered added to the queue).
InitialCapacity
has been eliminated. Instead, construct your bdlcc::Deque
object and then use proctor access to call reserve
on the contained bsl::deque
to reserve the desired initial capacity. (Note that deque::reserve
is not part of the C++ standard, though bsl::deque
does implement it).bsl::deque
, automatically locking the mutex and updating the condition variables as necessary.length
accessor is provided, eliminating the need to access the underlying thread-unsafe container to obtain its length.This section illustrates intended use of this component.
First, declarer the struct WordData
. Imagine it contains some data one wants to process:
Then, create the function that will produce a WorkData
object:
Next, declare WorkRequest
, the type of object that will be stored in the container:
Then, create the function that will do work on a WorkRequest
object:
Next, create the functor that will be run in the consumer threads:
Then, create the functor that will be run in the producer threads:
Next, in main
, define the number of consumer and producer threads (these numbers must be equal).
Then, create our container:
Next, create the array of thread handles for the threads we will spawn:
Now, spawn all the consumers and producers:
Finally, join all the threads after they finish and confirm the container is empty afterward:
First, we declare the Event
type, that will be contained in our bdlcc::Deque
object.
Next, in main
, define the number of threads:
Then, declare out bdlcc::Deque
object, the set of handles of the subthreads, and our barrier object:
Next, spawn the worker threads:
Then, wait on the barrier, that will set all the subthreads running:
Now, loop to pop the events off the deque, and keep track of how many e_COMPLETE
events have been popped. When this equals the number of subthreads, we are done.
Finally, perform some sanity checks: