QMutex for shared resources

It is almost an everyday experience seeing a problem rising from the fact that two or more guys competes on a resource available to both.

The resource could be a door, wide enough just to let pass a single person. How to decide who should wait and who could pass? In computer sciences we could use a mutex (short for MUTual EXclusion) to take that decision, and Qt implements this concept in the QMutex class.

In a multithreading environment, the standard output console is a good example of a shared resource for which different threads competes. To let this issue emerge, I have slightly modified the example of the previous post, where we played with a simple multithreading Qt application. I have made the class Writer moderately more complex, adding a private integer member, named value_, and I changed accordingly its ctor, to let the user code a way to set the new data member:
explicit Writer(const QString& mark, int value = 42) : mark_(mark), value_(value) {}
Besides, I changed the printing line in the Writer::run() method in this way:
std::cout << '[' << qPrintable(mark_) << value_ << ']';
Actually, I have also changed the main, passing 1, 2, and 3 as second parameter to the ctor for each Writer object, and this was the first output line I have got running the application:
[One[Three3]1][Two2][Three3][One1][Two2][Three3][One1][Two2][Three[One1]3][Two2]
As you can see, we have an issue. Often nothing bad happens, and we have the print just as we exptected: "[One1]". But sometimes we get funny results, like the very first chunk of characters: "[One[Three3]1]". What happened there is quite clear, Thread 1 was happily executing its printing line, had already sent the first part of it, "[One", to the standard output console, when Thread 3 jumped in, printed all its stuff, "[Three3]", before giving back the control to Thread 1 that, completely unaware of the interruption, sent the final part, "1]", to the console.

This is usually bad, as you can imagine. And the worst part is that the unexpected behaviour happens randomly, making it a possible nightmarish debugging issue.

Luckly we have ways to avoid this scenario. Using QMutex we could redesign our Writer class in this way:
#ifndef WRITER_H
#define WRITER_H

#include <QThread>
#include <QString>
#include <QMutex>

class Writer : public QThread
{
public:
explicit Writer(const QString& mark, int value = 42) : mark_(mark), value_(value) {}

void run();
private:
QString mark_;
int value_;

static QMutex mio_;
};

#endif // WRITER_H

The focal point is that we added a unique QMutex object available to all the class instances - that is the sense of making it static - so that it would be the access ruler to the IO. Its name should suggest its raison d'être: mio_ as mutex for I/O.

Being a static variable it has to be defined somewhere in the source code, I put it in the writer.cpp file, just before the run() definition:
QMutex Writer::mio_;

void Writer::run()
{
for(int i = 0; i < 20; ++i)
{
mio_.lock(); // 1.
std::cout << '[' << qPrintable(mark_) << value_ << ']';
mio_.unlock(); // 2.

msleep(200);
}
}

1. Any thread executing the run() code, before printing - using the shared resource std::cout - tries to acquire a lock on the mutex. If another thread has locked the mutex, the current one patiently sits waiting for it.
2. As soon as the thread has finished using the shared resource, it should release the lock on the mutex, giving a chance to the other threads to access it.

As we can see running this new version of the application, this approach works fine, and it is ok in such a simple case, but it is far from being perfect. We rely too heavily on the programmer to match correctly lock()/unlock() on a mutex and we are not considering the risk of an exception happening just after we locked a mutex and before we could unlock it. In the next post we'll see a more robust solution.

No comments:

Post a Comment