Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bslmt_readlockguard
[Package bslmt]

Provide generic scoped guards for read synchronization objects. More...

Namespaces

namespace  bslmt

Detailed Description

Outline
Purpose:
Provide generic scoped guards for read synchronization objects.
Classes:
bslmt::ReadLockGuard automatic locking-unlocking for read access
bslmt::ReadLockGuardUnlock automatic unlocking-locking for read access
bslmt::ReadLockGuardTryLock automatic non-blocking locking-unlocking
bslmt::LockReadGuard DEPRECATED
See also:
Component bslmt_lockguard, Component bslmt_writelockguard
Description:
This component provides generic guards, bslmt::ReadLockGuard, bslmt::ReadLockGuardUnlock, and bslmt::ReadLockGuardTryLock, to automatically lock and unlock an external synchronization object for reading. The synchronization object can be any type (e.g., bslmt::ReaderWriterLock) that provides the following methods:
  void lockRead();
  void unlock();
Both bslmt::ReadLockGuard and bslmt::ReadLockGuardUnlock implement the "construction is acquisition, destruction is release" idiom. During construction, bslmt::ReadLockGuard automatically calls lockRead on the user-supplied object, and unlock when it is destroyed (unless released). bslmt::ReadLockGuardUnlock does the opposite -- it invokes the unlock method when constructed and the lockRead method when destroyed.
A third type of guard, bslmt::ReadLockGuardTryLock, attempts to acquire a lock, and if acquisition succeeds, releases it upon destruction. Since the acquisition is done at construction time, it is not possible to return a value to indicate success. Rather, the bslmt::ReadLockGuardTryLock contains a pointer to the synchronization object if tryLock succeeds, and is null otherwise. The synchronization object can be any type (e.g., bslmt::Mutex or bslmt::RecursiveMutex) that provides the following methods:
  int tryLockRead();
  void unlock();
Note that objects of none of these guard types assumes ownership of the synchronization object provided at construction. Also note that objects of all of the guard types may be constructed with a null lock whereby the constructed guard objects guard no lock. The destructor of each of the guard types has no effect if no lock is under management.
Behavior of the release Method:
Like all BDE guard classes, each of the three bslmt::ReadLockGuard* classes provides a release method that terminates the guard's management of any lock object that the guard holds. The release method has no effect on the state of the lock object.
In particular, bslmt::ReadLockGuard::release does not unlock the lock object under management. If a user wants to release the lock object and unlock the lock object (because the lock is no longer required before the guard goes out of scope), the following idiom can be used:
  // 'guard' is an existing guard of type 'bslmt::ReadLockGuard<my_RLock>',
  // created in a scope that we do not control.

  {
      // ... Do work that requires the lock.

      // We know that the lock is no longer needed.

      my_RLock *rlock = guard.release();

      // 'rlock' is no longer managed, but is *still* *locked*.

      rlock->unlock();

      // ... Do work that does not require the lock.
  }
Usage:
Use this component to ensure that in the event of an exception or exit from any point in a given scope, the synchronization object will be properly unlocked. The following function, errorProneFunc, is overly complex, not exception safe, and contains a bug.
  static void errorProneFunc(const my_Object *obj, my_RWLock *rwlock)
  {
      rwlock->lockRead();
      if (someCondition) {
          obj->someMethod();
          rwlock->unlock();
          return;                                                   // RETURN
      } else if (someOtherCondition) {
          obj->someOtherMethod();
          // MISTAKE! forgot to unlock rwlock
          return;                                                   // RETURN
      }
      obj->defaultMethod();
      rwlock->unlock();
      return;
  }
The function can be rewritten with a cleaner and safer implementation using a guard object. The safeFunc function is simpler than errorProneFunc, is exception safe, and avoids the multiple calls to unlock that can be a source of errors.
  static void safeFunc(const my_Object *obj, my_RWLock *rwlock)
  {
      bslmt::ReadLockGuard<my_RWLock> guard(rwlock);
      if (someCondition) {
          obj->someMethod();
          return;                                                   // RETURN
      } else if (someOtherCondition) {
          obj->someOtherMethod();
          // OK, rwlock is automatically unlocked
          return;                                                   // RETURN
      }
      obj->defaultMethod();
      return;
  }
When blocking while acquiring the lock is not desirable, one may instead use a bslmt::ReadLockGuardTryLock in the typical following fashion:
  static int safeButNonBlockingFunc(const my_Object *obj, my_RWLock *rwlock)
      // Perform task and return positive value if locking succeeds.  Return
      // 0 if locking fails.
  {
      const int RETRIES = 1; // use higher values for higher success rate
      bslmt::ReadLockGuardTryLock<my_RWLock> guard(rwlock, RETRIES);
      if (guard.ptr()) { // rwlock is locked
          if (someCondition) {
              obj->someMethod();
              return 2;                                             // RETURN
          } else if (someOtherCondition) {
              obj->someOtherMethod();
              return 3;                                             // RETURN
          }
          obj->defaultMethod();
          return 1;                                                 // RETURN
      }
      return 0;
  }
If the underlying lock object provides an upgrade to a lock for write (as does bslmt::ReaderWriterLock with the upgradeToWriteLock function, for example), this can be safely used in conjunction with bslmt::ReadLockGuard, as long as the same unlock method is used to release both kinds of locks. The following method illustrates this usage:
  static void safeUpdateFunc(my_Object *obj, my_RWLock *rwlock)
  {
      const my_Object *constObj = obj;
      bslmt::ReadLockGuard<my_RWLock> guard(rwlock);
      if (someUpgradeCondition) {
          rwlock->upgradeToWriteLock();
          obj->someUpgradeMethod();
          return;                                                   // RETURN
      } else if (someOtherCondition) {
          constObj->someOtherMethod();
          // OK, rwlock is automatically unlocked
          return;                                                   // RETURN
      }
      constObj->defaultMethod();
      return;
  }
In the above code, the call to upgradeToWriteLock is not necessarily atomic, as the upgrade may release the lock for read and be interrupted before getting a lock for write. It is possible to guarantee atomicity (as does bslmt::ReaderWriterLock if the lockReadReserveWrite function is used instead of lockRead, for example), but the standard constructor should not be used. Instead, the lockReadReserveWrite lock function should be used explicitly, and the guard constructed with an object which is already locked. The following method illustrates this usage:
  static void safeAtomicUpdateFunc(my_Object *obj, my_RWLock *rwlock)
  {
      const my_Object *constObj = obj;
      rwlock->lockReadReserveWrite();
      const int PRELOCKED = 1;
      bslmt::ReadLockGuard<my_RWLock> guard(rwlock, PRELOCKED);
      if (someUpgradeCondition) {
          rwlock->upgradeToWriteLock();
          obj->someUpgradeMethod();
          return;                                                   // RETURN
      } else if (someOtherCondition) {
          constObj->someOtherMethod();
          return;                                                   // RETURN
      }
      constObj->defaultMethod();
      return;
  }
Note that in the code above, the function rwlock->lockRead() is never called, but is nevertheless required for the code to compile.
Instantiations of bslmt::ReadLockGuardUnlock can be interleaved with instantiations of bslmt::ReadLockGuard to create both critical sections and regions where the lock is released.
  void f(my_RWLock *rwlock)
  {
      bslmt::ReadLockGuard<my_RWLock> guard(rwlock);

      // critical section here

      {
          bslmt::ReadLockGuardUnlock<my_RWLock> guard(rwlock);

          // rwlock is unlocked here

      } // rwlock is locked again here

      // critical section here

  } // rwlock is unlocked here
Care must be taken so as not to interleave guard objects in such a way as to cause an illegal sequence of calls on a lock (two sequential lock calls or two sequential unlock calls on a non-recursive read/write lock).