在循环中正确使用 QThread
Proper usage of QThread in loops
我正在尝试更深入地了解 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();
}
运行() 函数如下所示:
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,这似乎是我的问题的解决方案,但从那里我无法收集线程中的计算结果。也许我错过了什么?
一些补充说明:
目前我正在使用重新实现 运行() 函数的方法。我也尝试过 moveToThread() 方法,但我更迷茫了。因此,如果那是解决方案,有些人可以尝试向我解释它是如何工作的。
我正在检查 Qt 的文档:https://doc.qt.io/qt-5/qthread.html
但是在这里我有一种感觉,即使是 'operate' 信号的发射在示例中也丢失了(否则为什么需要连接它?)
提前感谢您的回答!
使用 Qt Concurrent
框架中的 QtConcurrent::run()
。
extern int aFunction();
QThreadPool pool;
QFuture<int> future = QtConcurrent::run(&pool, aFunction);
然后您可以从未来的对象中检索结果
int result = future.result();
@abhlib 是正确的,这正是 Qt Assistant 为您提供的,它应该完美地满足您的需求。但我仍然敢于尝试澄清一些关于实施细节的特殊问题。
这是一个简短的示例,它演示了 Qt 为您提供的一些基本便利 classes(不要在实际代码中那样使用它!):
#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...在处理线程时,您通常有四种选择:
- 继承
QThread
- 使用'QThread' +
moveToThread()
- Subclass
QRunnable
并使用 QThreadPool
- 只需使用
QtConcurrent
中的任何内容
我想,与解释我的示例相比,您对这些用例的一些提示更感兴趣(Qt 文档非常全面)。
继承QThread
这曾经是处理线程的老套路,前段时间甚至表明了作者糟糕的编码风格。在 "there we've got a background thread with calculations and there it does something" 这样的情况下工作正常。您可以尝试一下,这是警告:您可以使用属于一个线程的 some 方法和 some[= 轻松创建一些复杂的 class 80=] 给另一个。恕我直言,这就是调试地狱。
使用moveToThread()
这是实现后台计算的一种巧妙方法。您获得了一个具有独立事件循环的线程,所有让您感到困惑的管理都是通过信号和插槽完成的,请查看文档 here.
Qt并发助手
嗯,这里有几个helper,都是比较高级的,大部分实现了Map-Reduce-Filter多线程模式。关键概念是你有一个 Qt 线程池(默认的全局线程池或出于某种原因你自己的线程池)并且你获得 QFuture
class 的实例,以延迟的方式获得你的计算结果。
至于 QFuture
没有信号,还有 QFutureWatcher
和 QFutureSynchronizer
class 可以为您提供有关任务状态的信号(实际上,您需要知道他们什么时候完成)。在一些更复杂的问题中,您可能希望报告和监控任务的进度,这也是可能的。 QFuture
也支持 pausing/resuming/cancelling.
好吧,那些复杂的选项需要更多的实现细节,但是老 QtConcurrent::run
做的 正是您所询问的:它接收一些可调用的并返回一个 QFuture
无法暂停或取消的实例,但仍然使您免于几乎所有 "low-level" 线程管理。您配置一个线程池,它为工作线程提供您的任务,剩下的就是等待结果的合适方式。试一试!
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 对象的所有权,如果您不能,请正确保护多线程内存管理躲开它。
祝你好运!
我正在尝试更深入地了解 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();
}
运行() 函数如下所示:
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,这似乎是我的问题的解决方案,但从那里我无法收集线程中的计算结果。也许我错过了什么?
一些补充说明: 目前我正在使用重新实现 运行() 函数的方法。我也尝试过 moveToThread() 方法,但我更迷茫了。因此,如果那是解决方案,有些人可以尝试向我解释它是如何工作的。 我正在检查 Qt 的文档:https://doc.qt.io/qt-5/qthread.html 但是在这里我有一种感觉,即使是 'operate' 信号的发射在示例中也丢失了(否则为什么需要连接它?)
提前感谢您的回答!
使用 Qt Concurrent
框架中的 QtConcurrent::run()
。
extern int aFunction();
QThreadPool pool;
QFuture<int> future = QtConcurrent::run(&pool, aFunction);
然后您可以从未来的对象中检索结果
int result = future.result();
@abhlib 是正确的,这正是 Qt Assistant 为您提供的,它应该完美地满足您的需求。但我仍然敢于尝试澄清一些关于实施细节的特殊问题。
这是一个简短的示例,它演示了 Qt 为您提供的一些基本便利 classes(不要在实际代码中那样使用它!):
#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...在处理线程时,您通常有四种选择:
- 继承
QThread
- 使用'QThread' +
moveToThread()
- Subclass
QRunnable
并使用QThreadPool
- 只需使用
QtConcurrent
中的任何内容
我想,与解释我的示例相比,您对这些用例的一些提示更感兴趣(Qt 文档非常全面)。
继承QThread
这曾经是处理线程的老套路,前段时间甚至表明了作者糟糕的编码风格。在 "there we've got a background thread with calculations and there it does something" 这样的情况下工作正常。您可以尝试一下,这是警告:您可以使用属于一个线程的 some 方法和 some[= 轻松创建一些复杂的 class 80=] 给另一个。恕我直言,这就是调试地狱。
使用moveToThread()
这是实现后台计算的一种巧妙方法。您获得了一个具有独立事件循环的线程,所有让您感到困惑的管理都是通过信号和插槽完成的,请查看文档 here.
Qt并发助手
嗯,这里有几个helper,都是比较高级的,大部分实现了Map-Reduce-Filter多线程模式。关键概念是你有一个 Qt 线程池(默认的全局线程池或出于某种原因你自己的线程池)并且你获得 QFuture
class 的实例,以延迟的方式获得你的计算结果。
至于 QFuture
没有信号,还有 QFutureWatcher
和 QFutureSynchronizer
class 可以为您提供有关任务状态的信号(实际上,您需要知道他们什么时候完成)。在一些更复杂的问题中,您可能希望报告和监控任务的进度,这也是可能的。 QFuture
也支持 pausing/resuming/cancelling.
好吧,那些复杂的选项需要更多的实现细节,但是老 QtConcurrent::run
做的 正是您所询问的:它接收一些可调用的并返回一个 QFuture
无法暂停或取消的实例,但仍然使您免于几乎所有 "low-level" 线程管理。您配置一个线程池,它为工作线程提供您的任务,剩下的就是等待结果的合适方式。试一试!
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 对象的所有权,如果您不能,请正确保护多线程内存管理躲开它。
祝你好运!