- Introduction
- Multithreaded Programming in Object-Oriented Languages
- Doomed If You Do, and Doomed If You Don't
- Locks as Permits
- Whose Lock Is It, Anyway?
- Improving External Locking
- Conclusion
Multithreaded Programming in Object-Oriented Languages
First, let's recap some typical multithreaded programming idioms in object-oriented languages.
Typically, object-oriented programs use object-level locking by associating a synchronization object (mutex) with each object that is susceptible to be shared between threads. Then, code that manipulates the state of the object can synchronize by locking that object. Inside a synchronized section, the mutex associated with the object is locked, and consequently that object's fields can be accessed safely.
In C++, this fundamental idiom is typically implemented with a helper Lock object. Consider, for example, modeling a bank account class that supports simultaneous deposits and withdrawals from multiple locations (arguably the "Hello, World" of multithreaded programming). In the code below, guard's constructor locks the passed-in object this, and guard's destructor unlocks this.
class BankAccount { Mutex mtx_; int balance_; public: void Deposit(int amount) { Lock guard(*this); balance_ += amount; } void Withdraw(int amount) { Lock guard(*this); balance_ -= amount; } void AcquireMutex() { mtx_.Acquire(); } void ReleaseMutex() { mtx_.Release(); } };
Lock is a separate class whose implementation is independent of BankAccount's semantics. All Lock has to do with the BankAccount object is call a method such as AcquireMutex() in its constructor and ReleaseMutex() in its destructor. This applies to any type that supports locking. To work with all such types of objects, Lock is usually a template class, implemented as shown below:
template <class T> class Lock { T& obj_; public: Lock(T& obj) : obj_(obj) { obj.AcquireMutex(); } ~Lock() { obj_.ReleaseMutex(); } };
So now you use Lock<BankAccount> to lock a BankAccount, Lock<Widget> to lock a Widget, and so on. Notice that the template approach generates a different type of lock for each type locked (Lock<BankAccount> is a distinct type from Lock<Widget> and so on). As in a detective movie, this detail will later become of paramount importance.
The object-level locking idiom doesn't cover the entire richness of a threading model. For example, the model above is quite deadlock-prone when you try to coordinate multi-object transactions. Nonetheless, object-level locking is useful in many cases, and in combination with other mechanisms can provide a satisfactory solution to many threaded access problems in object-oriented programs.