Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bslmt_qlock
[Package bslmt]

Provide small, statically-initializable mutex lock. More...

Namespaces

namespace  bslmt

Detailed Description

Outline
Purpose:
Provide small, statically-initializable mutex lock.
Classes:
bslmt::QLock Small, statically-initializable intra-process mutex
bslmt::QLockGuard Automatic locking-unlocking of bslmt::QLock
See also:
Component bslmt_mutex, bslmt_atomictypes, Component bslmt_lockguard, Component bslmt_once
Description:
This component defines a portable and efficient lock for ensuring that only one thread at a time enters a specific "critical region" -- a section of code that accesses a shared resource -- bslmt::Qlock and its associated bslmt::QLockGuard. The functionality of the bslmt::QLock class overlaps those of the bslmt::Mutex and bsls::SpinLock classes, but with different usage and performance characteristics, as shown in the following grid:
                                    | QLock | Mutex | SpinLock
 -----------------------------------+-------+-------+---------
 Memory footprint                   | small | large | small
 Cost of construction/destruction   | cheap | costly| cheap
 Statically initializable           | yes   | no    | yes
 Speed at low contention            | fast  | fast  | fast
 Speed at high contention           | slow  | fast  | very slow
 Suitable for long critical regions | yes   | yes   | no
 Fair                               | yes   | no    | no
The performance trade-offs for a QLock are quite different than those for a conventional mutex. QLocks are best suited for low-contention applications where large numbers of locks may be needed. For example, a node-based data structure that needs a lock for each node can benefit from the small size and low initialization cost of a QLock compared to that of a conventional mutex. A bslmt::Mutex object cannot be initialized statically because some platforms (e.g., Windows XP) do not have a native statically-initializable mutex type. A bslmt::QLock object, in contrast is statically initializable on all platforms.
The performance characteristics of a QLock are very similar to those of a SpinLock. However, a QLock is much more suitable than a SpinLock for situations where the critical region is more than a few instructions long. Also, although QLocks are best for low-contention situations, they do not degrade nearly as badly as SpinLocks if there is a lot of contention for the lock. They also use significantly fewer CPU cycles in high-contention situations than do SpinLocks.
A unique characteristic of QLocks is that they are fair. If there is contention for a lock, each thread is given the lock in the order in which it requested it. Consequently, every thread competing for the lock will get a chance before any other thread can have a second turn; no thread is ever "starved" out of the critical region. This fairness comes at a cost, however, in that the scheduler is given less leeway to schedule threads in the most efficient manner.
The bslmt::QLockGuard Class:
A bslmt::QLock is different from other locking classes such as bslmt::Mutex and bsls::SpinLock in that it cannot be manipulated except through the auxiliary bslmt::QLockGuard class. The reason for this limited interface is that a QLock requires a small amount of additional storage for each thread that is holding or waiting for the lock. The bslmt::QLockGuard provides this extra storage efficiently on the stack.
In typical usage, a bslmt::QLockGuard is created as a local (stack) variable, acquires the lock in its constructor and releases the lock in its destructor. If the lock is in use at construction time, then the current thread blocks until the lock becomes available. Although the QLock itself is intended to be shared among multiple threads, the guard object must never be used by more than one thread at a time. When multiple threads want to acquire the same QLock, each must use its own bslmt::QLockGuard object.
bslmt::QLockGuard also provides the following manipulators typical of locking classes:
  void lock();    // Acquire the lock, waiting if necessary
  int tryLock();  // Acquire the lock if possible.  Fail if lock is in use.
  void unlock();  // Free the lock.
As with other types of mutexes, only one thread may hold the lock at a time. Other threads attempting to call lock will block until the lock becomes available. However, it is important to remember that the manipulators listed above are only pass-through operations on the shared bslmt::QLock object. In other words, upon return from calling lock on a bslmt::QLockGuard object, a thread has actually acquired the lock to the underlying bslmt::QLock.
Although it is only a proxy for the actual QLock, lock/unlock/tryLock interface of bslmt::QLockGuard allows it to be treated as though it were itself a lock. In particular, it is possible to instantiate the bslmt::LockGuard and bslmt::LockGuardUnlock class templates using bslmt::QLockGuard. This layering of guard classes is useful for creating regions where the QLock is locked or unlocked. For example, if a thread acquires a QLock and then needs to temporarily relinquish it, it could use a bslmt::LockGuardUnlock as follows:
  void Node::update()
  {
     bslmt::QLockGuard qguard(&d_qlock);  // 'd_qlock' is a 'bslmt::QLock'.
     readLunarState();
     if (d_moonIsFull) {
         // Free lock while we sleep
         bslmt::LockGuardUnlock<bslmt::QLockGuard> unlock(&qguard)
         sleep(TWENTY_FOUR_HOURS);
     }
     // Lock has been re-acquired
     ...
  }
The behavior is undefined if unlock is invoked from a thread that did not successfully acquire the lock, or if lock is called twice in a thread without an intervening call to unlock (i.e., bslmt::QLockGuard is non-recursive).
Usage:
This section illustrates intended use of this component.
Example 1: Using bslmt::QLock to Implement a Thread-Safe Singleton:
For this example, assume that we have the need to use the string "Hello" repeatedly in the form of an bsl::string object. Rather than construct the string each time we use it, it would be nice to have only one copy so that we can amortize the memory allocation and construction cost over all the uses of the string. It is thus logical to have a single, static variable (a singleton) of type bsl::string initialized with the value, "Hello". Unfortunately, as this is a multithreaded application, there is the danger that more than one thread will attempt to initialize the singleton simultaneously, causing a memory leak at best and memory corruption at worse. To solve this problem, we use a bslmt::QLock to synchronize access to the singleton.
We begin by wrapping the singleton in a function:
  const bsl::string& helloString()
  {
This function defines two static variables, a pointer to the singleton, and a QLock to control access to the singleton. Note that both of these variables are statically initialized, so there is no need for a run-time constructor and hence no danger of a race condition among threads. The need for static initialization is the main reason we choose to use bslmt::QLock over bslmt::Mutex:
      static const bsl::string *singletonPtr = 0;
      static bslmt::QLock qlock = BSLMT_QLOCK_INITIALIZER;
Before checking the status of the singleton pointer, we must make sure that we are not accessing the pointer at the same time that some other thread is modifying the pointer. We do this by acquiring the lock by constructing a bslmt::QLockGuard object:
      bslmt::QLockGuard qlockGuard(&qlock);
Now we are inside the critical region. If the pointer has not already been set, we can initialize the singleton knowing that no other thread is manipulating or accessing these variables at the same time. Note that this critical region involves constructing a variable of type bsl::string. This operation, while not ultra-expensive, is too lengthy for comfortably holding a spinlock. Again, the characteristics of bslmt::QLock are superior to the alternatives for this application. (It is worth noting that the QLock concept was created specifically to permit this kind of one-time processing. See also bslmt_once.)
      if (! singletonPtr) {
          static bsl::string singleton("Hello");
          singletonPtr = &singleton;
      }
Finally, we return a reference to the singleton. The destructor for bslmt::QLockGuard will automatically unlock the QLock and allow another thread into the critical region.
      return *singletonPtr;
  }
The following test program shows how our singleton function can be called. Note that hello1 and hello2 have the same address, demonstrating that there was only one string created.
  int usageExample1()
  {
      const bsl::string EXPECTED("Hello");

      const bsl::string& hello1 = helloString();
      assert(hello1 == EXPECTED);

      const bsl::string& hello2 = helloString();
      assert(hello2  == EXPECTED);
      assert(&hello2 == &hello1);

      return 0;
  }