QThread在循环中的正确用法

问题描述 投票:0回答:2

我正在尝试更深入地了解QThread,但有种感觉,我确实没有在循环中正确使用它。

我有一些耗时的计算,需要运行几次。我正在尝试以下(简化示例):

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();
}

run()函数如下所示:

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);

因此,我有几个问题:

  • 如何避免启动过多线程?例如。如果我想将其数量限制为QThread :: idealThreadCount()。
  • 我如何检查每个线程是否完成?
  • 如何避免代码泄漏?

我已经尝试过QThreadPool,这似乎是我所遇到问题的一种解决方案,但是从那里我无法在线程中收集计算结果。也许我错过了一些东西?

一些附加说明:当前,我正在使用重新实现run()函数的方法。我也尝试过moveToThread()方法,但是我什至更多。因此,如果这是解决方案,则可以尝试向我解释它的工作方式。我正在查看Qt的文档:https://doc.qt.io/qt-5/qthread.html但是在这里,我感觉示例中甚至没有“操作”信号的发射(否则为什么需要连接它?)

感谢您提前回答!

c++ qt qthread
2个回答
3
投票

使用QtConcurrent::run()框架中的Qt Concurrent

extern int aFunction();
QThreadPool pool;
QFuture<int> future = QtConcurrent::run(&pool, aFunction);

然后您可以从将来的对象中检索结果

int result = future.result();

0
投票

@@ abhlib是正确的,这正是Qt Assistant为您提供的,它应该完全满足您的需求。但我仍然敢于尝试澄清您对实现细节的一些特殊问题。

这里有一个简短的示例,它演示了Qt为您提供的一些基本便利类(不要在实际代码中以这种方式使用它!):

#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 ...处理线程时,通常有四个选择:

  1. 继承QThread
  2. 使用'QThread'+ moveToThread()
  3. 子类QRunnable并使用QThreadPool
  4. 只需使用QtConcurrent中的任何内容

我想,关于这些用例的一些提示比说明我的示例更有趣(Qt文档非常全面)。

继承QThread

这曾经是处理线程的老式方法,前一段时间它甚至表明作者的不良编码风格。在“我们有一个带有计算的后台线程并且在其中执行某些操作”的情况下,效果很好。您可能已经尝试过了,这是警告:您可以通过属于一个线程的some方法和属于另一个线程的some轻松地创建一些复杂的类。恕我直言,这就是调试地狱。

使用moveToThread()

这是实现后台计算的一种简洁方法。您将获得一个具有独立事件循环的线程,所有困惑您的管理都是通过信号和插槽完成的,请检查文档here

Qt并发助手

嗯,这里有几个助手,它们都是比较高级的,并且大多数都实现Map-Reduce-Filter多线程模式。关键概念是您拥有一个Qt线程池(默认的全局池或出于某种原因而拥有自己的线程池),并且获得了QFuture类的实例,这些实例以延迟的方式获得计算结果。

[ar QFuture没有信号,也存在QFutureWatcherQFutureSynchronizer类,它们为您提供有关任务状态的信号(实际上,您需要知道它们何时大部分完成)。在某些更复杂的问题中,您可能希望报告和监视任务的进度,这也是可能的。 QFuture还支持暂停/恢复/取消。

嗯,那些复杂的选项需要更多的实现细节,但是老式的QtConcurrent::run确实做到了[[您所要询问的内容:它接收了一些可调用的内容并返回了无法暂停或取消的QFuture实例,但是仍然使您摆脱了几乎所有的“低级”线程管理。您配置了一个线程池,它为工作线程提供任务,剩下的就是等待结果的合适方法。试试看!

QRunnable

这几乎是相同的把戏,只是使用QThreadPool的另一种便捷方式。您可以使用与run()map()等相同的方式进行配置。但是,如果没有QFuture,您将缺乏一种非常方便的方法来处理结果和完成操作,可能对某些[[really

自定义任务。下面是标准示例:

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);

方法的选择取决于您,但是您最好对所有方法都熟悉,这样您就可以根据要解决的问题来改变线程工具。 


关于内存泄漏,这是一个更通用的主题,不是特别多线程的主题。 Practice&Valgrind将为您提供帮助,但是如果需要关于该主题的一些非常简短的建议,则可能是以下几种:使用智能指针,检查Qt对象的所有权,在无法进行保护的情况下正确保护多线程内存管理避开它。

祝你好运!

© www.soinside.com 2019 - 2024. All rights reserved.