The Guard Idiom: Enhancing the Scoped Locking Idiom

Eric D. Crahen

More flexible locks for your multithreaded programs.

Introduction

The Scoped Locking Idiom ensures that a resource, typically a lock such as a mutex or some other synchronization primitive, is acquired and held for the duration of the scope of a block of code and is automatically released when that scope ends. The Scoped Locking Idiom makes use of the RAII (resource acquisition is initialization) concept. When a block of code begins, an object, commonly referred to as a guard or sentry, is constructed and attempts to acquire a resource. This marks the beginning of the locking scope. When that object goes out of scope, it is destroyed, releasing the resource it previously held and ending the locking scope.

// A simple Scoped Lock
temaplte <typename LOCK>
class ScopedLock {
  LOCK& _lock;
public:
  ScopedLock(LOCK& lock) : _lock(lock) {
    _lock.acquire();
  }
  ~ScopedLock() {
    _lock.release();
  }
};
This is a very simple and effective technique that can increase the safety and robustness of code that must be synchronized by eliminating programmer errors. By placing a sentry object in some block of code, a locking scope can be defined that will match the scope of that sentry object's lifetime. A good deal of effort can be eliminated; a programmer no longer needs to ensure the lock is released at each point control may leave the block. This task can be quite tedious, especially when considering code where exceptions may be thrown with that block. Eliminating the effort involved in writing and maintaining this code is very desirable.

The Scoped Locking Idiom is limited only in that it is designed for use with simple locking scopes that are locked with the lifetime of a sentry object. The Guard Idiom extends the Scoped Locking concept by introducing a means to control the extent of the locking scope so that it is not so closely bound to that single object's lifetime.

The Guard Idiom

The Guard Idiom is fundamentally very similar to the Scoped Locking Idiom. However, they differ in one important aspect. Scoped locks (objects employing the Scoped Locking Idiom) are designed to work independently, which is all that is required when dealing with a simple locking scope. Guards (objects employing the Guard Idiom), on the other hand, are designed to cooperate with one another, which introduces some flexibility and makes some new locking behaviors possible. By allowing guards to interact, locking scopes can now be defined that do not necessarily begin and end with the creation and destruction of a single object.

The Guard Idiom can help improve the robustness and eliminate programmer error for the following kinds of locking scopes.

Designing the Guard Idiom

The implementation of a guard consists of four elements:

1. A Lockable object: an object with acquire/release semantics that provides an abstraction for the real locking mechanism, typically a Mutex.

2. A sentry: denotes a scope by its lifetime. However this scope does not define the locking scope as it does with the Scoped Locking Idiom. Rather, it holds a reference to Lockable object that will be operated on and presents a simple interface for the user.

3. The LockHolder object: maintains and shares any state that is associated with a Lockable object. This can be thought of as a stateful wrapper for a Lockable object.

4. The locking policy: specifies the details of how a sentry will act, or how it will interact with another sentry. This involves encapsulating the details of determining when to invoke the Lockable object's acquire or release methods, as well as any other state change that might be appropriate.

Implementing the Guard Idiom

Most implementations of the Scoped Locking Idiom are realized as a template that provides a very simple and convenient syntax. One goal in implementing a guard is produce a template that maintains the simple, easy-to-use syntax that will provide the power to introduce the additional kinds of flexibility in creating and using locking scopes.

The implementation described in this article lets you define Lockable objects as they are normally defined in the Scoped Locking Idiom. The Lockable objects used throughout this article follow a simple acquire/release protocol and conform to the following interface.

class Lockable {
public:
  virtual ~Lockable() {}
  virtual void acquire() = 0;
  virtual void release() = 0;
 };
Some variants may elect to use a non-virtual interface instead.

State is maintained by an instance of the LockHolder class. This class wraps a Lockable object, adding a small amount of state information and a few functions that work with that information.

template <class LockType>
class LockHolder {
  LockType &_lock;
  bool _enabled;
 public:
  template <class T>
  LockHolder(T& t) :
    _lock(extract(t)._lock), _enabled(true) { }
  LockHolder(LockHolder& holder) :
    _lock(holder._lock), _enabled(true) { }
  LockHolder(LockType& lock) :
    _lock(lock), _enabled(true) { }
  ~LockHolder() throw() {}
  void disable() { _enabled = false; }
  bool isDisabled() { return !_enabled; }
  LockType& getLock() { return _lock; }
 protected:
  template <class T>
  static LockHolder& extract(T& t) {
    return static_cast<LockHolder&>(t);
  }
};
Locking policies are the most complicated portion of the implementation. The locking policies, implemented as classes with static template member functions for compatibility with older compilers, define four functions.

// Signatures of a locking policies functions
template <class LockType>
createScope(LockHolder<LockType>& l);
template <class LockType>
destroyScope(LockHolder<LockType>& l);
template <class LockType1, class LockType2>
shareScope(LockHolder<LockType1>& l1,
  LockHolder<LockType2>& l2);
template <class LockType1, class LockType2>
transferScope(LockHolder<LockType1>& l1,
  LockHolder<LockType2>& l2);
These functions will be delegated to by the sentry, which is realized by the Guard template, at the appropriate times, as determined by syntax. Syntax will provide important clues to the sentry regarding when it is appropriate to take action. For instance, a guard that is created using a Lockable object as its only parameter is a clue that a new basic locking scope is being created, and createScope is delegated to in the policy. Creating a guard via a copy constructor is a clue that an existing locking scope is about to be shared with, and possibly modified by, another guard.

Creating a policy that provides the classic Scoped Locking behavior is very straightforward.

class ClassicScope {
public:
  template <class LockType>
  static void createScope
    (LockHolder<LockType>& l) {
    l.getLockable().acquire();
  }
  template <class LockType>
  static void destroyScope
    (LockHolder<LockType>& l) {
    l.getLockable().release();
  }
};
You can create a Guard template that preserves the desired syntax by extending the LockHolder class and delegating to the policy class, as shown in Listing 1. Listing 2 shows some examples of the Guard Idiom in use.

An Example

The GuardedObject template attached illustrates a simple application of the ideas described in this article. The GuardObject template is intended not as a full smart pointer but rather as a wrapper for a single object. This simple demonstration shows how one might apply the Guard idiom to create locking proxies. This notion could be taken further and used to implement a full smart pointer with locking proxies. However, the focus here is on how the locking proxies would work out and not on the details of reference counting, assignment operators, and all the other things you'd find in a real smart pointer.

Conclusion

The Guard Idiom adds a small level of indirection to the Scoped Locking Idiom and in doing so achieves an enormous amount of flexibility in controlling the extent of the locking scope. A complete implementation is available electronically, for free, as a part of the ZThreads library (<http://zthread.sourceforge.net>).

References

[1] Douglas C. Schmidt. "Strategized Locking, Thread-safe Decorator, and Scoped Locking: Patterns and Idioms for Simplifying Multithreaded C++ Components," C++ Report, September 1999.

[2] Bjarne Stroustrup. The C++ Programming Language, 3rd Edition (Addison-Wesley, 1998).

About the Author

Eric D. Crahen is a software developer for IBM. He can be reached at crahen@code-foo.com.