假设我调用QtConcurrent::run()
在工作线程中运行一个函数,并在该函数中动态分配几个QObject(供以后使用)。由于它们是在工作线程中创建的,因此它们的线程关联应该是工作线程的线程关联。但是,一旦工作线程终止,QObject线程关联就不再有效了。
问题:Qt会自动将QObject移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效线程?
答案 0 :(得分:6)
QThread
没有记录在任何QObject
完成时自动移动,所以我认为我们已经可以得出结论,它没有这样的事情。这种行为会非常令人惊讶,与API的其他部分不一致。
为了完整起见,我测试了Qt 5.6:
QObject o;
{
QThread t;
o.moveToThread(&t);
for (int i = 0; i < 2; ++i)
{
t.start();
QVERIFY(t.isRunning());
QVERIFY(o.thread() == &t);
t.quit();
t.wait();
QVERIFY(t.isFinished());
QVERIFY(o.thread() == &t);
}
}
QVERIFY(o.thread() == nullptr);
回想一下QThread
不是线程,它管理一个线程。
当QThread
完成后,它将继续存在,并且其中的对象继续存在于其中,但它们不再处理事件。可以重新启动QThread
(不推荐),此时将恢复事件处理(因此相同的QThread
可以管理不同的线程)。
当QThread
被销毁时,其中的对象不再具有任何线程关联。 documentation并不能保证这一点,事实上说&#34;您必须确保在删除QThread
之前删除线程中创建的所有对象。&#34 ;
假设我调用
QtConcurrent::run()
在工作线程中运行一个函数,在该函数中我动态分配几个QObject(供以后使用)。由于它们是在工作线程中创建的,因此它们的线程关联应该是工作线程的线程关联。但是,一旦工作线程终止,QObject线程关联就不再有效了。
QThread
在这种情况下不会终止。当QtConcurrent::run
生成的任务完成后,其运行的QThread
将返回QThreadPool
,并可能在后续调用QtConcurrent::run
时重复使用,{{1住在那里的QObject
继续住在那里。
QThread
您可能希望在将对象移回QThreadPool::globalInstance()->setMaxThreadCount(1);
QObject *o = nullptr;
QThread *t = nullptr;
QFuture<void> f = QtConcurrent::run([&] {
o = new QObject;
t = o->thread();
QVERIFY(t == QThread::currentThread());
});
f.waitForFinished();
QVERIFY(t == o->thread());
QVERIFY(t->isRunning());
f = QtConcurrent::run([=] {
QVERIFY(t == QThread::currentThread());
});
f.waitForFinished();
之前手动移出QThread
,或者只是不要使用QThreadPool
。拥有一个比任务更长的QtConcurrent::run
任务构造QtConcurrent::run
是一个值得怀疑的设计,任务应该是自包含的。正如@Mike所述,QObject
使用的QThread
没有事件循环。
答案 1 :(得分:5)
但是,一旦工作线程终止,QObject线程关联就不再有效了。
工作线程在函数调用后 NOT 终止。使用QtConcurrent::run
的重点是在re-using threads上执行全局线程池(或一些提供的QThreadPool
)上的大量小任务,以避免为每个创建和销毁线程的开销这些小任务之一。除了在所有可用内核之间分配计算外。
您可以尝试查看Qt的源代码以查看how QtConcurrent::run
is implemented。您会看到它最终会调用RunFunctionTaskBase::start
,它基本上会调用QThreadPool::start
并使用QRunnable
来调用最初传递给QtConcurrent::run
的函数。
现在要说明的是,QThreadPool::start
是implemented,只需将QRunnable
添加到队列中,然后尝试从其中唤醒其中一个线程线程池(waiting用于将新QRunnable
添加到队列中。这里需要注意的是,来自线程池的线程没有运行事件循环(它们不是按照这种方式设计的),它们只是在队列中执行QRunnable
而没有更多(它们这是出于性能原因而以这种方式实现的。)
这意味着,当您在QObject
中执行的函数中创建QtConcurrent::run
时,您只需创建一个QObject
,它位于没有事件循环的线程中,来自docs,限制包括:
如果没有运行事件循环,则不会将事件传递给对象。例如,如果您在线程中创建
QTimer
对象但从不调用exec()
,则QTimer
将永远不会发出其timeout()
信号。拨打deleteLater()
也不会有效。 (这些限制也适用于主线程。)
TL; DR: QtConcurrent::run
在全局QThreadPool
(或提供的)的线程中运行函数。这些线程不运行事件循环,它们只是等待QRunnable
运行。因此,居住在这些线程的线程中的QObject
不会传递任何事件。
在documentation中,他们使用QThread
(possibly, with an event loop and a worker object)并使用QtConcurrent::run
作为两种独立的多线程技术。它们并不意味着混合在一起。所以,线程池中没有工作者对象,这只是在寻找麻烦。
问题:Qt会自动将QObjects移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效的线程?
我认为在以这种方式查看事物之后,答案很明显,Qt会自动 NOT 将QObject
移动到任何线程中。该文档已警告过在没有事件循环的情况下在QObject
中使用QThread
,这就是它。
您可以随意将它们移动到您喜欢的任何主题。但请记住,moveToThread()
有时会导致问题。例如,如果移动工作对象涉及移动QTimer
:
请注意,将重置该对象的所有活动计时器。计时器首先在当前线程中停止,然后在targetThread中重新启动(具有相同的间隔)。因此,不断在线程之间移动对象可以无限期地推迟计时器事件。
结论:我认为您应该考虑使用运行其事件循环的自己的QThread
,并在那里创建您的工作人员QObject
,而不是使用{{1} }。这种方式比移动QtConcurrent
要好得多,并且可以避免使用当前方法可能产生的许多错误。查看comparison table of multi-threading technologies in Qt并选择最适合您用例的技术。如果您只想执行一次调用函数并获取其返回值,则仅使用QObject
。如果您希望与该线程进行永久性交互,则应切换为使用您自己的QtConcurrent
与工作人员QThread
进行交互。
答案 2 :(得分:3)
Qt会自动将QObject移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效的线程?
否,Qt不会自动将QObject
移动到父线程中。
此行为未明确记录,因此我对Qt框架source code,主分支进行了一次小型调查。
QThread
从QThreadPrivate::start
开始:
unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg)
{
...
thr->run();
finish(arg);
return 0;
}
void QThread::terminate()
{
Q_D(QThread);
QMutexLocker locker(&d->mutex);
if (!d->running)
return;
if (!d->terminationEnabled) {
d->terminatePending = true;
return;
}
TerminateThread(d->handle, 0);
d->terminated = true;
QThreadPrivate::finish(this, false);
}
在两种情况下,线程终结都在QThreadPrivate::finish
中完成:
void QThreadPrivate::finish(void *arg, bool lockAnyway)
{
QThread *thr = reinterpret_cast<QThread *>(arg);
QThreadPrivate *d = thr->d_func();
QMutexLocker locker(lockAnyway ? &d->mutex : 0);
d->isInFinish = true;
d->priority = QThread::InheritPriority;
bool terminated = d->terminated;
void **tls_data = reinterpret_cast<void **>(&d->data->tls);
locker.unlock();
if (terminated)
emit thr->terminated();
emit thr->finished();
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
QThreadStorageData::finish(tls_data);
locker.relock();
d->terminated = false;
QAbstractEventDispatcher *eventDispatcher = d->data->eventDispatcher;
if (eventDispatcher) {
d->data->eventDispatcher = 0;
locker.unlock();
eventDispatcher->closingDown();
delete eventDispatcher;
locker.relock();
}
d->running = false;
d->finished = true;
d->isInFinish = false;
if (!d->waiters) {
CloseHandle(d->handle);
d->handle = 0;
}
d->id = 0;
}
它将QEvent::DeferredDelete
事件发布到清除QObject::deleteLater
,而不是删除了QThreadStorageData::finish(tls_data)
和eventDispatcher
时清理的TLS数据。之后QObject
将不会收到此帖子中的任何事件,但QObject
的线程关联性保持不变。看到void QObject::moveToThread(QThread *targetThread)
的实现以了解线程关联性如何变化,这很有意思。
void QThreadPrivate::finish(void *arg, bool lockAnyway)
的实施表明QObject
的{{1}}的线程亲和力未被QThread
更改。
答案 3 :(得分:2)
虽然这是一个老问题,但我最近提出了同样的问题,并且刚刚使用QT 4.8和一些测试来回答它。
AFAIK您无法使用QtConcurrent :: run函数中的父项创建对象。我尝试了以下两种方法。让我定义一个代码块,然后我们将通过选择POINTER_TO_THREAD来探索行为。
一些伪代码会显示我的测试
Class MyClass : public QObject
{
Q_OBJECT
public:
doWork(void)
{
QObject* myObj = new QObject(POINTER_TO_THREAD);
....
}
}
void someEventHandler()
{
MyClass* anInstance = new MyClass(this);
QtConcurrent::run(&anInstance, &MyClass::doWork)
}
忽略潜在的范围界定问题......
如果POINTER_TO_THREAD
设置为this
,那么您将收到错误,因为this
将解析为指向生成主线程的anInstance
对象的指针, 不 QtConcurrent为其调度的线程。你会看到像...这样的东西。
Cannot create children for a parent in another thread. Parent: anInstance, parents thread: QThread(xyz), currentThread(abc)
如果POINTER_TO_THREAD
设置为QObject::thread()
,那么您将收到错误,因为它将解析为anInstance
所在的QThread对象,并且不是线程QtConcurrent已为其分派。你会看到像...这样的东西。
Cannot create children for a parent in another thread. Parent: QThread(xyz), parents thread: QThread(xyz), currentThread(abc)
希望我的测试对其他人有用。如果有人知道如何获得QtConcurrent运行方法的QThread的指针,我会很有兴趣听到它!
答案 4 :(得分:1)
我不确定Qt是否会自动更改线程关联。但即使它确实如此,唯一合理的线程就是主线程。我会在自己的线程函数结尾处推送它们。
myObject->moveToThread(QApplication::instance()->thread());
现在,只有当对象使用发送和接收信号等事件处理时,这才有意义。
答案 5 :(得分:1)
虽然Qt文档似乎没有指定您可以通过跟踪线程完成之前和之后QObject::thread()
返回的内容而找到的行为。