Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Simplified coding with Guards

The use of mutexes rapidly becomes complicated when exceptions are introduced. To make sure that the mutex is always released, you must ensure that each possible exception path includes a call to release( ). In addition, any function that has multiple return paths must carefully ensure that it calls release( ) at the appropriate points.

These problems can be easily solved by using the fact that a stack-based (auto) object has a destructor that is always called regardless of how you exit from a function scope. In the ZThread library, this is implemented as the Guard template. The Guard template creates objects that acquire( ) a Lockable object when constructed and release( ) that lock when destroyed. Guard objects created on the local stack will automatically be destroyed regardless of how the function exits and will always unlock the Lockable object. Here s the above example reimplemented using Guards:

//: C11:GuardedEvenGenerator.cpp {RunByHand}
// Simplifying mutexes with the Guard template.
//{L} ZThread
#include <iostream>
#include "EvenChecker.h"
#include "zthread/ThreadedExecutor.h"
#include "zthread/Mutex.h"
#include "zthread/Guard.h"
using namespace ZThread;
using namespace std;
 
class GuardedEvenGenerator : public Generator {
unsigned int currentEvenValue;
Mutex lock;
public:
GuardedEvenGenerator() { currentEvenValue = 0; }
~GuardedEvenGenerator() {
cout << "~GuardedEvenGenerator" << endl;
}
int nextValue() {
Guard<Mutex> g(lock);
++currentEvenValue;
Thread::yield();
++currentEvenValue;
return currentEvenValue;
}
};
 
int main() {
EvenChecker::test<GuardedEvenGenerator>();
} ///:~
 

Note that the temporary return value is no longer necessary in nextValue( ). In general, there is less code to write, and the opportunity for user error is greatly reduced.

An interesting feature of the Guard template is that it can be used to manipulate other guards safely. For example, a second Guard can be used to temporarily unlock a guard:

//: C11:TemporaryUnlocking.cpp
// Temporarily unlocking another guard.
//{L} ZThread
#include "zthread/Thread.h"
#include "zthread/Mutex.h"
#include "zthread/Guard.h"
using namespace ZThread;
 
class TemporaryUnlocking {
Mutex lock;
public:
void f() {
Guard<Mutex> g(lock);
// lock is acquired
// ...
{
Guard<Mutex, UnlockedScope> h(g);
// lock is released
// ...
// lock is acquired
}
// ...
// lock is released
}
};
 
int main() {
TemporaryUnlocking t;
t.f();
} ///:~
 

A Guard can also be used to try to acquire a lock for a certain amount of time and then give up:

//: C11:TimedLocking.cpp
// Limited time locking.
//{L} ZThread
#include "zthread/Thread.h"
#include "zthread/Mutex.h"
#include "zthread/Guard.h"
using namespace ZThread;
 
class TimedLocking {
Mutex lock;
public:
void f() {
Guard<Mutex, TimedLockedScope<500> > g(lock);
// ...
}
};
 
int main() {
TimedLocking t;
t.f();
} ///:~
 

In this example, a Timeout_Exception will be thrown if the lock cannot be acquired within 500 milliseconds.

Synchronizing entire classes

The ZThread library also provides a GuardedClass template to automatically create a synchronized wrapper for an entire class. This means that every member function in the class will automatically be guarded:

//: C11:SynchronizedClass.cpp {-dmc}
//{L} ZThread
#include "zthread/GuardedClass.h"
using namespace ZThread;
 
class MyClass {
public:
void func1() {}
void func2() {}
};
 
int main() {
MyClass a;
a.func1(); // Not synchronized
a.func2(); // Not synchronized
GuardedClass<MyClass> b(new MyClass);
// Synchronized calls, only one thread at a time allowed:
b->func1();
b->func2();
} ///:~
 

Object a is a not synchronized, so func1( ) and func2( ) can be called at any time by any number of threads. Object b is protected by the GuardedClass wrapper, so each member function is automatically synchronized and only one function per object can be called any time.

The wrapper locks at a class level of granularity, which may affect performance.[151] If a class contains some unrelated functions, it may be better to synchronize those functions internally with two different locks. However, if you find yourself doing this, it means that one class contains groups of data that may not be strongly associated. Consider breaking the class into two classes.

Guarding all member functions of a class with a mutex does not automatically make that class thread-safe. You must carefully consider all threading issues in order to guarantee thread safety.

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire