I l@ve RuBoard Previous Section Next Section

A.5 Locking Semantics in Object-Oriented Programming

Synchronization objects are associated with shared resources. In an object-oriented program, resources are objects. Therefore, in an object-oriented program, synchronization objects are associated with application objects.

It follows that each shared object should aggregate a synchronization object and lock it appropriately in every mutating member function, much as the BankAccount example does. This is a correct way to structure an object supporting multithreading. The structure fostering one synchronization object per object is known as object-level locking.

However, sometimes the size and the overhead of storing one mutex per object are too big. In this case, a synchronization strategy that keeps only one mutex per class can help.

Consider, for example, a String class. From time to time, you might need to perform a locking operation on a String object. However, you don't want each String to carry a mutex object; that would make Strings big and their copying costly. In this case, you can use a static mutex object for all Strings. Whenever a String object performs a locking operation, that operation will block all locking operations for all String objects. This strategy fosters class-level locking.

Loki defines two implementations of the ThreadingModel policy: ClassLevelLockable and ObjectLevelLockable. They encapsulate class-level locking and object-level locking semantics, respectively. The synopsis is presented here.



template <typename Host>


class ClassLevelLockable


{


public:


   class Lock


   {


   public:


      Lock();


      Lock(Host& obj);


      ...


   };


   ...


};





template <typename Host>


class ObjectLevelLockable


{


public:


   class Lock


   {


   public:


      Lock(Host& obj);


      ...


   };


   ...


};


Technically, Lock keeps a mutex locked. The difference between the two implementations is that you cannot construct an ObjectLevelLockable<T>::Lock without passing a T object to it. The reason is that ObjectLevelLockable uses per-object locking.

The Lock nested class locks the object (or the entire class, in the case of ClassLevelLockable) for the lifetime of a Lock object.

In an application, you inherit one of the implementations of ThreadingModel. Then you use the inner class Lock directly. For example,



class MyClass : public ClassLevelLockable <MyClass>


{


   ...


};


Table A.1. Implementations of ThreadingModel
Class Template Semantics
SingleThreaded No threading strategy at all. The Lock and ReadLock classes are empty mock-ups.
ObjectLevelLockable Object-level locking semantics. One mutex per object is stored. The Lock inner class locks the mutex (and implicitly the object).
ClassLevelLockable Class-level locking semantics. One mutex per class is stored. The Lock inner class locks the mutex (and implicitly all objects of a type).

The exact locking strategy depends on the ThreadingModel implementation you choose to derive from. Table A.1 summarizes the available implementations.

You can define synchronized member functions very easily, as outlined in the following example:



class BankAccount : public ObjectLevelLockable<BankAccount>


{


public:


   void Deposit(double amount, const char* user)


   {


      Lock lock(*this);


      ... perform deposit transaction ...


   }


   void Withdraw(double amount, const char* user)


   {


      Lock lock(*this);


      ... perform withdrawal transaction ...


   }


   ...


};


You no longer have any problem with premature returns and exceptions; the correct pairing of lock/unlock operations on the mutex is guaranteed by language invariants.

The uniform interface supported by the dummy interface SingleThreaded gives syntactic consistency. You can write your code assuming a multithreading environment, and then easily change design decisions by modifying the threading model.

The ThreadingModel policy is used in Chapter 4 (Small-Object Allocation), Chapter 5 (Generalized Functors), and Chapter 6 (Implementing Singletons).

    I l@ve RuBoard Previous Section Next Section