Callback for multithreading

The basic Qt multithreading we have seen before was a bit too basic. Its extensions for showing how and why to use QMutex, and then QMutexLocker do not change much of its substance. Let's complicate it a bit to make it a tad more interesting.

In the original example we have a class, Writer, that extends QThread and that in its run() method writes something. But what if we would like that all the Writer object would run a function that could be set at runtime? A possible solution to this requirement is based on function pointers.

In my main file I have written this function:

namespace
{
static QMutex mio; // 1.

void doSomething(const char* msg, int i)
{
QMutexLocker qml(&mio); // 2.
std::cout << msg << ", thread id " << QThread::currentThreadId() // 3.
<< " value " << i << std::endl;
}
}

1. Since we are about to work in a multithreaded environment, and since we'll have many threads competing on the output console, we'll use a mutex to avoid mixups.
2. Using a lock on the mutex we ensure it would be correctly released at the end of the block.
3. We print the thread id and the data passed by the caller.

We want all the object instantiating Writer calling this doSomething() function (and possibly other functions having the same prototype). To do that, I have refactored the Writer class definition in this way:
#ifndef WRITER_H
#define WRITER_H

#include <QThread>

typedef void(*MY_FUN)(const char*, int); // 1.

class Writer : public QThread
{
public:
explicit Writer(int value);
void run();

static void setCallback(MY_FUN fun); // 2.
private:
static MY_FUN callback_; // 3.
};

#endif // WRITER_H

1. MY_FUN has been typedeffed as pointer to a function returning void and having two input parameters, a pointer to const char, and an integer.
2. using this static method we could set the callback function available to all Writer objects.
3. the callback_ function pointer is static: all the Writer object refer to the same one.

The static data members have to be defined in the source code, so in writer.cpp I added the callback_ definition:
MY_FUN Writer::callback_ = NULL;
Initially there is no callback function pointer defined.

And here is the Writer functions:
void Writer::setCallback(MY_FUN fun)
{
callback_ = fun;
}

Writer::Writer(int value)
{
if(callback_) // 1.
callback_("ctor", value);
}

void Writer::run()
{
for(int i = 0; i < 20; ++i)
{
if(callback_)
callback_("Running", i); // 2.

msleep(200);
}
}

1. As always when working with pointers, it is a good idea to check them before trying to use them.
2. Being called from run(), we ensure that our callback function is called in the new thread.

Not a big change in the main function:
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

Writer::setCallback(&doSomething);

Writer w1(1);
Writer w2(2);
Writer w3(3);

w1.start();
w2.start();
w3.start();

w1.wait();
w2.wait();
w3.wait();

// terminates in a couple of seconds
QTimer::singleShot(12000, &a, SLOT(quit()));

return a.exec();
}

What we have done, basically, has been externalizing the job done by each sub-thread, so that it could be easily changed even at runtime.

2 comments:

  1. Bumped to this article. Pointer is not atomic, so both check in Writer::run() and initialization via Writer::setCallback() give us race condition. Though it can give no exception: pointer is not declared as volatile, so compiler optimization can simply delete check within cycle.

    ReplyDelete
    Replies
    1. Hello Yuri, thank you for leaving a comment.

      Yes, it makes sense stressing the point that setCallback() and run() Writer methods are not designed to work concurrently. The first is meant to be used in initialization phase, the latter during each Writer object lifetime.

      It would be interesting to improve this example to make the access to callback pointer threadsafe, so that we could reset it on the fly.

      Delete