Still, we should design the code carefully - if it is not trivial as the one I am using for these examples - to ensure that all the calls to lock()/unlock() match correctly, an we should pay extra care on the possible exceptions that could break that matching.
RAII (Resouce Acquisition Is Initialization) paradigm helps us to solve this concern, keeping our code even more readable. And QMutexLocker is the Qt class designed to do that.
The RAII idea is that when we need to acquire a resource we should think of this as a initialization, so, in our case we should think to acquiring a lock on the mutex as the creation of an object (of type QMutexLocker) that accept the mutex as initialization parameter. Doing that we save ourselves the hassle of remembering to unlock() the mutex, since that would be automatically done by the QMutexLocker dtor.
And what if an exception happens just after the QMutexLocker object is allocated? No problem at all. The C++ mechanism of stack unwinding ensures that all objects on the stack are correctly disposed.
All this safety is achieved rewriting the run() function from the previous post in this way:
for(int i = 0; i < 20; ++i)
std::cout << '[' << qPrintable(mark_) << value_ << ']';
If you are wondering why I have put the QMutexLocker object and the print statements in a block of their own, you should remember that we want the locking on the mutex to be effective in the shortest possible region of the code. Since we are using a QMutexLocker object to rule it, we create an artificial block, just to get the effect of making that object exiting of its scope as soon as possible, unlocking the mutex.