PDA

View Full Version : Working properly with threads / QThreads



Momergil
5th January 2014, 13:53
Hello!

By now I've read the QtAssistant documentation regarding QThreads and even used a little of them in some projects, but to be honest I'm still not sure I'm working with them in the correct, safe method. To be more specific, I'm still not sure I understood the way by wich the run() method should be used alongisde the functions exec(), start(), quit(), terminate() and wait() as well as the use of flags to control the thread's event loop.

To give some context, now I'm working on a project where I need to emit a signal each 250 ms and I decided it would be better to use a thred to do the work. So I need a event loop that, once the thread is started, will do the job, plus a previous configuration. The run() function, therefore, looked like this:



void DataBrain::run()
{
qDebug() << "run()";

switch (source.principal)
{
case SourceInternet:
switch (source.internet)
{
case SourceYahoo:
connect(this,SIGNAL(signalUpdateData()),this,SLOT( slotUpdateYahoo()));
break;

case SourceGoogle:
connect(this,SIGNAL(signalUpdateData()),this,SLOT( slotUpdateGoogle()));
D_TODO("DataBrain::run() | SourceGoogle");
break;

default:
M_BUG_OCCURANCE("DataBrain::run() | SourceInternet");
break;
}
break;

case SourceRealTime:
D_TODO("DataBrain::run() | SourceRealTime");
break;

case SourceLocal:
M_BUG_OCCURANCE("DataBrain::run()() | currentSource == SourceLocal");
break;
}

while(true)
{
emit signalUpdateData();

msleep(250);
}

qDebug() << "run end";
}


In doing so, I was expecting, given what I read in the QtAssistant data about QThread, that once I call the start() function from this thread, the run() function would be executed, doing the configuration and then running the event loop eternaly till a quit() or terminate() function was called, with the detail that if I call terminate(), the last qDebug() would never be called, while by using quit() it would since QtAssistant says that quit() ends the thread's event loop and finish it correctly (by which I understood: runs run() till the end). What I noticed, though, in a first moment was not just that the last qDebug() was never called, but also that if I called quit() and then start() again, the thread would immediately start again inside the while(true) code, never passing again by the configuration procedure and the first qDebug(). This was making quite the problem for my code till I was able to fix this, despite I still don't know exactly how ^^ (I guess it was because I put a "wait(500);" after my call of quit().

I was beginning to think that my problem was solved when I read this topic in QtCentre: [http://www.qtcentre.org/threads/55907-Why-I-cannot-stop-a-thread?highlight=qDebug+thread], where anda_skoa wrote that the proper way of handling a thread is to create a stop flag (that is, something like "while(flag)" instead of "while(true)"). I've done that already in previous works, but I still would like to know exactly why shouldn't I use my way, with the calling of quit(), instead of this flag-based method.

More than that, it's still not clear to me when should I use terminate() instead of quit(), given the fact that that method is avaliable for us programmers to use despite being risky. Can I call it at least in a thread's destructor? And for what exactly the method exec() is used in thread programming, given the fact that there is the function start()? And also how exactly the method quit() works? And since we are already talking about threads, when exactly it's appropriate to use QSemaphore instead of QMutex/QMutexLocker and vice-versa?



Thanks and sorry for too many questions in one topic.

Momergil

anda_skoa
5th January 2014, 14:35
In order for quit to work the thread will need to run its event loop.
That is what the QThread::run() base implementation does. If one overwrites run() then one can still start the event loop by calling exec().



void MyThread::run()
{
// do some setup

exec(); // runs until quit is called
}


I would stronlgy advise against calling terminate(), it can leave the program in state that it can not recover from.

I am not really sure what your full use case is but calling a function regularily can easily be done without threads, e.g. using QTimer.



DataBrain::DataBrain(Source source, QObject *parent) : QObject(parent)
{
QTimer *timer = new QTimer(this);

switch (source.principal)
{
case SourceInternet:
switch (source.internet)
{
case SourceYahoo:
connect(timer,SIGNAL(timeout()),this,SLOT(slotUpda teYahoo()));
break;

case SourceGoogle:
connect(time,SIGNAL(timeout()),this,SLOT(slotUpdat eGoogle()));
D_TODO("DataBrain::run() | SourceGoogle");
break;

default:
M_BUG_OCCURANCE("DataBrain::DataBrain() | SourceInternet");
break;
}
break;

case SourceRealTime:
D_TODO("DataBrain::DataBrain() | SourceRealTime");
break;

case SourceLocal:
M_BUG_OCCURANCE("DataBrain::DataBrain() | currentSource == SourceLocal");
break;
}

timer->start(250);
}

Or putting that into a DataBrain::start() method, etc.



And since we are already talking about threads, when exactly it's appropriate to use QSemaphore instead of QMutex/QMutexLocker and vice-versa?

A binary semaphore, i.e. a QSemaphore which only ever has two values (0 and 1), is basically equivalent with QMutex. However, since there is no QMutexLocker equivalent, I would recommend using QMutex when the use case is to protect a generic critical section against concurrent access.

The semaphore becomes interesting when it is being used with values n > 1, waiting for n threads to finish, or using it to enforce a resource usage limit, etc.

Cheers,
_

Momergil
6th January 2014, 15:32
Hello anda_skoa,

regarding the timer, I used it before, but since I wanted a little bit more of time precision, not to mention to relieve the main thread a little bit, I decided to put it inside a thread :)


Thanks for the reply,

Momergil

anda_skoa
6th January 2014, 23:29
Using a timer doesn't mean you can't use it in a thread.

If you get this working like suggested in a QObject without a thread, then you can simply create a thread and move the object to it

e.g.from


DataBrain *dataBrain = new DataBrain;
dataBrain->start();

to


DataBrain *dataBrain = new DataBrain;
QThread *thread = new QThread(this);
dataBrain->moveToThread(dataBrain);
connect(thread, SIGNAL(started()), dataBrain, SLOT(start()));
connect(thread, SIGNAL(finished()), dataBrain, SLOT(deleteLater()));


If you still want to go for the run() re-implementation method, just ignore the signal/slot code that you currently have and call the methods from within your loop.

Cheers,
_