Provide an assert macro for verifying reader-writer lock status.
More...
Namespaces |
namespace | bslmt |
Detailed Description
- Outline
-
-
- Purpose:
- Provide an assert macro for verifying reader-writer lock status.
-
- Classes:
-
- Macros:
BSLMT_READERWRITERLOCKASSERT_IS_LOCKED | test in non-opt modes |
BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_SAFE | test in safe mode |
BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_OPT | test in all modes |
BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ | test in non-opt modes |
BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_SAFE | test in safe mode |
BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_OPT | test in all modes |
BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE | test in non-opt modes |
BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_SAFE | test in safe mode |
BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_OPT | test in all modes |
- See also:
- bslmt_lockassert, Component bslmt_readerwriterlock, Component bslmt_readerwritermutex
-
- Description:
- This component provides macros for asserting that a reader-writer lock is locked. It does not distinguish between locks held by the current thread or other threads. If the macro is active in the current build mode, when the macro is called, if the supplied lock is unlocked, the assert handler installed for
BSLS_ASSERT
will be called. The assert handler installed by default will report an error and abort the task. Note that the type of lock (pointer) passed to each of these macros is determined at compile-time. See Requirements on the Lock Type below.
- The nine macros defined by the component are analogous to the macros defined by
BSLS_ASSERT
: +---------------------------------------------------+------------------=+
| Macro | When Active |
+===================================================+===================+
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED' | When |
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ' | 'BSLS_ASSERT' |
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE' | is active. |
+---------------------------------------------------+-------------------+
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_SAFE' | When |
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_SAFE' | 'BSLS_ASSERT_SAFE'|
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_SAFE'| is active. |
+---------------------------------------------------+-------------------+
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_OPT' | When |
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ_OPT' | 'BSLS_ASSERT_OPT' |
|'BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE_OPT' | is active. |
+---------------------------------------------------+-------------------+
In build modes where any one of these macros is not active, the presence of the macros has no effect.
- If any of these asserts are in effect and fail (because the reader-writer lock in question was unlocked), the behavior parallels the behavior of the assertion macros defined in
bsls_assert.h
-- bsls::Assert::invokeHandler
is called, with a source code expression, the name of the source file, and the line number in the source file where the macro was called. If the default handler is installed, this will result in an error message and an abort.
-
- Caveat: False Positives:
- Preconditions on locks typically require that the lock exist and is held by the calling thread. Unfortunately, lock ownership is not recorded in the lock and cannot be confirmed. The absence of any lock when the calling thread should hold one is certainly a problem; however, the existence of a lock does not guarantee that the complete precondition is met.
-
- Requirements on the Lock Type:
- This system of macros accept pointers to reader-write lock objects that provide the methods:
-
isLocked
,
-
isLockedRead
, and
-
isLockedWrite
- Two compatible classes are:
- Although the required methods are typically
const
-qualified (i.e., "accessor" methods), that is not a requriement. Some client lock classes may implement these methods in terms of tryLock
/unlock
methods that require non-'const' access to the lock.
-
- Usage:
- This section illustrates intended use of this component.
-
- Example 1: Checking Consistency Within Private Methods:
- This example is an generalization of
bslmt_mutexassert
|Example 1: Checking Consistency Within a Private Method. In that example, a mutex was used to control access. Here, the (simple) mutex is replaced with a bslmt::ReaderWriterLock
that is allows multiple concurrent access to the queue when conditions allow.
- Sometimes multithreaded code is written such that the author of a function requires that a caller has already acquired a lock. The
BSLMT_READERWRITERLOCKASSERT_IS_LOCKED*
family of assertions allows the programmers to detect, using defensive programming techniques, if the required lock has not been acquired.
- Suppose we have a fully thread-safe queue that contains
int
values, and is guarded by an internal bslmt::ReaderWriterLock
object.
- First, we define the container class:
class MyThreadSafeQueue {
bsl::deque<int> d_deque;
mutable bslmt::ReaderWriterLock
d_rwLock;
int popImp(int *result);
bsl::pair<int, double> getStats() const;
public:
int pop(int *result);
void popAll(bsl::vector<int> *result);
void purgeAll(double limit);
void push(int value);
template <class INPUT_ITER>
void pushRange(const INPUT_ITER& first, const INPUT_ITER& last);
double mean() const;
bsl::size_t numElements() const;
};
Notice that this version of the MyThreadSafeQueue
class has two public accessors, numElements
and mean
, and an additional manipulator, purgeAll
.
- Then, we implement most of the manipulators:
int MyThreadSafeQueue::popImp(int *result)
{
BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE(&d_rwLock);
if (d_deque.empty()) {
return -1;
}
else {
*result = d_deque.front();
d_deque.pop_front();
return 0;
}
}
int MyThreadSafeQueue::pop(int *result)
{
BSLS_ASSERT(result);
d_rwLock.lockWrite();
int rc = popImp(result);
d_rwLock.unlockWrite();
return rc;
}
void MyThreadSafeQueue::popAll(bsl::vector<int> *result)
{
BSLS_ASSERT(result);
const int size = static_cast<int>(d_deque.size());
result->resize(size);
int *begin = result->begin();
for (int index = 0; index < size; ++index) {
int rc = popImp(&begin[index]);
BSLS_ASSERT(0 == rc);
}
}
void MyThreadSafeQueue::push(int value)
{
d_rwLock.lockWrite();
d_deque.push_back(value);
d_rwLock.unlockWrite();
}
template <class INPUT_ITER>
void MyThreadSafeQueue::pushRange(const INPUT_ITER& first,
const INPUT_ITER& last)
{
d_rwLock.lockWrite();
d_deque.insert(d_deque.begin(), first, last);
d_rwLock.unlockWrite();
}
Notice that these implementations are identical to those shown in bslmt_mutexassert
|Example 1 except that the lock
calls to the bslmt::Mutex
there have been changed here to lockWrite
calls on a bslmt::ReaderWriterLock
. Both operations provide exclusive access to the container.
- Also notice that, having learned the lesson of
bslmt_mutexassert
|Example 1, we were careful to acquire a write lock for the duration of each of these operation and to check the precondition of the private popImp
method by using the BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_WRITE
macro.
- Finally notice that we use the "normal" flavor of the macro (rather than the
*_SAFE
version) because this test is not particularly expensive.
- Next, we implement the accessor methods of the container:
double MyThreadSafeQueue::mean() const
{
d_rwLock.lockRead();
bsl::pair<int, double> result = getStats();
d_rwLock.unlockRead();
return result.second;
}
bsl::size_t MyThreadSafeQueue::numElements() const
{
d_rwLock.lockRead();
bsl::size_t numElements = d_deque.size();
d_rwLock.unlockRead();
return numElements;
}
Notice that each of these methods acquire a read lock for the duration of the operation. These locks allow shared access provided that the container is not changed, a reasonable assumption for these const
-qualified methods.
- Also notice that the bulk of the work of
mean
is done by the private method getStats
. One's might except the private method to confirm that a lock was acquired by using the BSLMT_READERWRITERLOCKASSERT_IS_LOCKED_READ
macro; however, the reason for creating that private method is so that it can be reused by the purgeAll
method, a non-'const' method that requires a write lock. Thus, getStats
is an occassion to use the BSLMT_READERWRITERLOCKASSERT_IS_LOCKED
check (for either a read lock or a write lock).
bsl::pair<int, double> MyThreadSafeQueue::getStats() const
{
BSLMT_READERWRITERLOCKASSERT_IS_LOCKED(&d_rwLock);
int numElements = d_deque.size();
if (0 == numElements) {
return bsl::make_pair(numElements, DBL_MIN);
}
int sum = bsl::accumulate(d_deque.cbegin(), d_deque.cend(), 0);
double mean = static_cast<double>(sum)
/ static_cast<double>(numElements);
return bsl::make_pair(numElements, mean);
}
Next, we implement the purgeAll
method: void MyThreadSafeQueue::purgeAll(double limit)
{
d_rwLock.lockWrite();
bsl::pair<int, double> results = getStats();
if (0 < results.first && limit < results.second) {
for (int i = 0; i < results.first; ++i) {
int dummy;
int rc = popImp(&dummy);
assert(0 == rc);
}
}
d_rwLock.unlockWrite();
}
Finally, we confirm that our accessors work as expected: void testEnhancedThreadSafeQueue()
{
MyThreadSafeQueue queue;
const int rawData[] = { 17, 3, -20, 7, 28 };
enum { k_RAW_DATA_LENGTH = sizeof rawData / sizeof *rawData };
queue.pushRange(rawData + 0, rawData + k_RAW_DATA_LENGTH);
assert(5 == queue.numElements());
assert(7 == queue.mean());
queue.push(100000);
queue.purgeAll(10);
assertV(queue.numElements(), 0 == queue.numElements());
assertV(queue.mean() , DBL_MIN == queue.mean());
}