【问题标题】:Proper usage of QThread in loops在循环中正确使用 QThread
【发布时间】:2019-11-19 21:03:48
【问题描述】:

我正在尝试更深入地使用 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


    【解决方案1】:

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

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

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

    int result = future.result();
    

    【讨论】:

    • 没错! QtConcurrent 是 OP 前两个问题的解决方案。还有QtConcurrent::run 的重载,您不必提供线程池(而是使用由Qt 管理的全局线程池)。但是,请注意future.result 会阻塞调用线程,直到计算完成,这可能不方便从 GUI 线程调用。当结果可用时,您可能希望使用QFutureWatcher 收到信号通知...
    【解决方案2】:

    @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

    QtConcurrent 助手

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

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

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

    QRunnable

    这几乎是相同的技巧,只是使用QThreadPool 的另一种便捷方式。您可以像使用run()map() 等一样配置它。但是如果没有QFuture,您将缺乏一种非常方便的方式来处理结果和完成,可能对某些人有用真的 自定义任务。这里的标准样本如下:

      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 对象的所有权,如果你不能正确保护多线程内存管理躲开它。

    祝你好运!

    【讨论】:

    • 我能够像您提到的那样使我的代码适应提到的 QtConcurent::run()。似乎工作正常:我得到的结果与以前使用 QThread 时相同,CPU 使用率(几乎)为 100%。 GUI 也保持响应,而且我最初的问题似乎也得到了解决。但是现在我面临一个不同的问题:与 QThread 解决方案相比,使用此解决方案的运行时间要慢一些。我认为必须有一些阻止操作,但是有什么方法可以找出为什么(或在哪里)它变慢了?
    • 哦,这个问题有点棘手。你在这两种情况下都有相同的代码吗?那里是否存在任何同步对象(互斥体等)?也许,大部分额外的时间你都在等待期货?甚至,也许,QtConcurrent::run 被调用了太多次,给你持续运行的线程带来了开销?您可以使用一个最小示例发布另一个问题,其中相同的代码变得更慢,如果没有看到它就很难判断。
    猜你喜欢
    • 1970-01-01
    • 2020-05-05
    • 2015-07-04
    • 1970-01-01
    • 2013-07-29
    • 2023-03-21
    • 2017-01-01
    • 2012-11-04
    • 1970-01-01
    相关资源
    最近更新 更多