Monitor Class
- Internally synchronized class Java synchronized object
Bank Class
// Very simple "bank" class
class Bank {
public:
void debit(const std::string &name, int amount);
void credit(const std::string &name, int amount);
void print(const std::string &name);
}
Naive Solution
- Mutex as private member
- Every member function which accesses shared data locks the mutex
class Bank {
// Mutex to protect the data
std::mutex mut;
// Shared data
...
public:
void credit(const std::string &name, int amount) {
std::lock_guard lck(mut);
}
void credit(const std::string &name, int amount) {
std::lock_guard lck(mut);
}
};
- Member functions may need to call other member functions
- Using multiple locks risks deadlock
- Transactions may involve multiple member function calls
- Results in many locking and unlocking operations
- Race conditions
- Potential data race
- Existing classes need to be modified
Monitor Class
- We write a wrapper class
- The Bank object is a data member
- The member functions lock a mutex and forward to the Bank object
class BankMonitor {
std::mutex mut;
Bank bank;
public:
void debit(const std::string& name, int amount) {
// Lock the mutex and forward the call
std::lock_guard lck(mut);
bank.debit(name, amount);
}
};
-
Works with any type
- Including classes that were not designed for threaded code
- No modifications needed to the class
-
Does not help callers who want to perform transactions
- Unnecessary locking
- Possibility of deadlock due to multiple locking
- Allows interruptions by other threads
Sophisticated Monitor Class
-
We make it generic
- The wrapped class's type is the template parameter
-
Functor class with overloaded
operator()- The argument is a callable object
- This contains the sequence of member function calls for the transaction
- We lock the mutex, then invoke the callable object
-
This will be a template member function
- The type parameter is the type of the callable object
// Monitor class - can wrap any type
template <typename T>
class Monitor {
// The object to be monitored
T data;
// Mutex to protect it
std::mutex mut;
public:
// Default / copy constructor
Monitor<T>(T data = T{}) : data(data) {}
// function call operator
// argument is a callable object of type Func
// which takes an argument of type T
template <typename Func>
auto operator() (Func func) {
std::lock_guard<std::mutex> lck_guard(mut);
// Call the function, protected by the lock
return func(data);
}
};
int main() {
// Transfer $1000 from Peter to Paul
// Must be done in a single transactiion
Monitor<Bank> mon;
mon([](Bank bank) {
// Call member functions all under the same lock
bank.debit("Peter", 1000);
bank.credit("Paul", 1000);
bank.print("Peter");
bank.print("Paul");
});
}
Advantages of Sophisticated Monitor Class
-
Works with any type
- Including classes that were not designed for threaded code
- No modifications needed to the class
-
Allows callers to perform transactions efficiently and safely
- Avoids unnecessary locking
- Avoids multiple locking
- Prevents interruptions by other threads
-
Gives callers complete freedom
- Which public member functions to call in a transaction
- What order to call them in