Search code examples
c++qtqthread

Proper usage of QThread in loops


I'm trying to getting a little bit deeper with the QThread but have the feeling I'm really not using it correctly in loops.

I have some time consuming calculations which I need to run several times. I'm trying with the following (simplified example):

for(int i = 0; i < 100; ++i)
{
    worker *task = new worker();

    connect(task, &worker::finished, this, &controller::calcFinished);
    connect(task, &worker::resultReady, this, &controller::handleResult);
    task->start();
}

The run() function looks like this:

variable = 0;

for(int i = 0; i < 5000; ++i)
{
    for(int j = 0; j < 500; ++j)
    {
        for(int k = 0; k < 500; k++)
        {
            ++variable;
        }
    }
}

emit resultReady(variable);

With this I have a several problem:

  • How shall I avoid starting too much threads? For eg. if I want to limit the number of it to the QThread::idealThreadCount().
  • How can I check if every thread finished?
  • How can avoid the code from leaks?

I've tried the QThreadPool, which seems to be a solution to the questions I have, but from there I was not able to collect the result of the calculation in the thread. Maybe I missed something with it?

Some additional notes: Currently I'm using the method where I re-implement the run() function. I've tried the moveToThread() method as well, but there I'm even more lost. So if that would be the solution some could try to explain it to me how is that working. I was checking the documentation from Qt: https://doc.qt.io/qt-5/qthread.html But here I have the feeling that even emitting of the 'operate' signal is missing in the example (otherwise why need to connect it?)

Thanks for the answers in advance!


Solution

  • @abhlib is correct, that's exactly what Qt Assistant offers you and it should fit your needs perfect. But still I'll dare and try to clarify some of the particular questions you've got about implementation details.

    Here goes a brief sample, which demonstrates some of the basic convenience classes Qt provides you (do NOT use it that way in real code!):

    #include <QCoreApplication>
    
    #include <QtConcurrent/QtConcurrentRun>
    #include <QFutureSynchronizer>
    #include <QFutureWatcher>
    
    #include <QDebug>
    
    long long job(int k)
    {
        int variable = 0;
    
        for(int i = 0; i < 5000; ++i)
        {
            for(int j = 0; j < 500; ++j)
            {
                for(int k = 0; k < 500; k++)
                {
                    ++variable;
                }
            }
        }
    
        return variable / k;
    }
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        QThreadPool myPool;
        myPool.setMaxThreadCount(QThread::idealThreadCount()); // Here goes thread number
        qInfo() << QString{"Thread count: %1"}.arg(QThread::idealThreadCount());
    
        constexpr int TASK_COUNT = 100;
        QFutureSynchronizer<long long> futureSync; // waits for multiple futures
        QMap<QSharedPointer<QFutureWatcher<long long>>, QFuture<long long>> myResults;
    
        for (int i = 1; i <= TASK_COUNT; ++i)
        {
            QFuture<long long> task = QtConcurrent::run(&myPool, job, i);
            // a shared pointer 'coz it cannot be copied, simplified example
            QSharedPointer<QFutureWatcher<long long>> watcher(new QFutureWatcher<long long>()); 
            watcher->setFuture(task);
    
            myResults.insert(watcher, task);
            QObject::connect(watcher.get(), &QFutureWatcher<long long>::finished, [num = i, &myResults, watcher](){
                qInfo() << QString{"%1 %2"}.arg(num).arg(myResults[watcher].result());
            }); // waiting for particular result
        }
    
        // this way we do not return until everything is done
        futureSync.waitForFinished(); 
        return a.exec();
    }
    

    Soo... when dealing with threads you have generally four options:

    1. Inherit QThread
    2. Use 'QThread' + moveToThread()
    3. Subclass QRunnable and use QThreadPool
    4. Just use anything from QtConcurrent

    I guess, some hint about those usecases would be of more interest for you than explaining my sample (Qt docs are pretty comprehensive).

    Inheriting QThread

    This used to be an old-fashioned way to deal with threads, some time ago it even indicated the author's bad coding style. Works fine in cases like "there we've got a background thread with calculations and there it does something". You could have tried it and here is the warning: you can easily create some complex class that way with some methods belonging to one thread and some to the other. IMHO, that's what debugging hell is.

    Using moveToThread()

    That is a neat way to implement background calculations. You get a thread with independent event loop and all the management that confused you is done via signals & slots, check the docs here.

    QtConcurrent helpers

    Well, there are several helpers here, all of them are comparatively high-level and most of them implement Map-Reduce-Filter multithreaded patterns. The key concept is that you have a Qt thread pool (a default global one or your own for some reason) and you get instances of QFuture class, that obtain your calculation results in a deferred way.

    As far ar QFuture has no signals, there also exist QFutureWatcher & QFutureSynchronizer classes that provide you with signals about tasks's states (actually, you need to know when they are finished mostly). In some more complicated problems you could probably want to report & monitor task's progress, which is also possible. QFuture also supports pausing/resuming/cancelling.

    Well, those sophisticated options need more implementaion details, but good old QtConcurrent::run does exactly what you've asked about: it receives some callable and returnes a QFuture instance that cannot be paused or cancelled, but still relieves you from almost all the "low-level" thread management. You configure a thread pool, it feeds worker threads with your tasks and all that's left is the suitable way to wait for results. Give it a try!

    QRunnable

    This is almost the same trick, just another convenient way to use QThreadPool. You can configure it the same way as with run(),map(), etc. But without QFuture you'll lack a very convenient way to deal with results and finishing, could be of use for some really custom tasks. Standard sample here is the following:

      class HelloWorldTask : public QRunnable
      {
          void run() override
          {
              qDebug() << "Hello world from thread" << QThread::currentThread();
          }
      };
    
      HelloWorldTask *hello = new HelloWorldTask();
      // QThreadPool takes ownership and deletes 'hello' automatically
      QThreadPool::globalInstance()->start(hello);
    

    Approach choice is up to you, but you'd better get familiar with all of them, so that you'd be able to vary your thread tools depending on the problem being solved.

    As for the memory leaks, it's a more general, not particularly multithreaded theme. Practice & Valgrind will help you through, but if it has to be some exremely short piece of advice on the topic, may it be the following: use smart pointers, check Qt objects' ownerships, protect multithreaded memory management properly if you can't avoid it.

    Good luck!