假设我调用QtConcurrent::run()
which 在工作线程中运行一个函数,并在该函数中动态分配几个 QObject(供以后使用)。由于它们是在工作线程中创建的,因此它们的线程亲和性应该是工作线程的亲和性。但是,一旦工作线程终止,QObject 线程亲和性应该不再有效。
问题:Qt 会自动将 QObject 移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效线程中?
假设我调用QtConcurrent::run()
which 在工作线程中运行一个函数,并在该函数中动态分配几个 QObject(供以后使用)。由于它们是在工作线程中创建的,因此它们的线程亲和性应该是工作线程的亲和性。但是,一旦工作线程终止,QObject 线程亲和性应该不再有效。
问题:Qt 会自动将 QObject 移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效线程中?
QThread
没有记录QObject
在完成时自动移动任何 s ,所以我认为我们已经可以得出结论,它没有做这样的事情。这种行为会非常令人惊讶,并且与 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);
回想一下,aQThread
不是线程,它管理一个线程。
当 aQThread
完成时,它继续存在,并且存在于其中的对象继续存在于其中,但它们不再处理事件。QThread
可以重新启动(不推荐),此时事件处理将恢复(因此同样可以QThread
管理不同的线程)。
当 aQThread
被销毁时,其中的对象不再具有任何线程关联性。文档并不能保证这一点,实际上是说“您必须确保在删除QThread
.
假设我调用
QtConcurrent::run()
which 在工作线程中运行一个函数,并在该函数中动态分配几个 QObject(供以后使用)。由于它们是在工作线程中创建的,因此它们的线程亲和性应该是工作线程的亲和性。但是,一旦工作线程终止,QObject 线程亲和性应该不再有效。
在这种QThread
情况下不会终止。当由QtConcurrent::run
完成产生的任务时,QThread
它正在运行的 将返回到QThreadPool
并且可以被后续调用重用QtConcurrent::run
,并且QObject
s 生活在其中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
任务构造QObject
是一个有问题的设计,任务应该是自包含的。正如@Mike 所指出的,所QThread
使用的 sQtConcurrent::run
没有事件循环。
但是,一旦工作线程终止,QObject 线程亲和性应该不再有效。
工作线程不会在您的函数调用后终止。使用的重点QtConcurrent::run
是在全局线程池(或提供的一些)上执行大量小任务,QThreadPool
同时重用线程以避免为这些小任务中的每一个创建和销毁线程的开销。除了在所有可用内核上分布计算。
您可以尝试查看 Qt 的源代码以了解如何QtConcurrent::run
实现。您将看到它最终RunFunctionTaskBase::start
调用.QThreadPool::start
QRunnable
QtConcurrent::run
现在我想要达到的一点是,QThreadPool::start
通过将添加QRunnable
到队列中,然后尝试从线程池中唤醒其中一个线程(正在等待将新线程QRunnable
添加到队列中)来实现。这里要注意的是,线程池中的线程没有运行事件循环(它们不是设计为以这种方式运行),它们只是为了执行QRunnable
队列中的 s 而仅此而已(它们是以这种方式实现的性能原因很明显)。
这意味着,当您QObject
在执行的函数中创建 a 时QtConcurrent::run
,您只是在创建一个QObject
存在于没有事件循环的线程中的docs,限制包括:
如果没有事件循环正在运行,则不会将事件传递给对象。例如,如果您
QTimer
在线程中创建一个对象但从不调用exec()
,QTimer
则永远不会发出它的timeout()
信号。打电话deleteLater()
也不行。(这些限制也适用于主线程。)
TL;DR: 在全局(或提供的)QtConcurrent::run
线程中运行函数。QThreadPool
这些线程不运行事件循环,它们只是等待QRunnable
s 运行。因此,QObject
生活在来自这些线程的线程中不会获得任何事件传递。
在文档中,他们将 using QThread
(可能带有事件循环和工作对象)和 usingQtConcurrent::run
作为两种独立的多线程技术。它们不应该混合在一起。所以,线程池中没有工作对象,这只是自找麻烦。
问题:Qt 会自动将 QObject 移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效线程中?
我认为在这样看之后,答案很明显,Qt 不会自动将QObject
s 移动到任何线程中。文档已经警告过QObject
在QThread
没有事件循环的情况下使用 a ,仅此而已。
您可以自由地将它们移动到您喜欢的任何线程。但请记住,这moveToThread()
有时会导致问题。例如,如果移动工作对象涉及移动 a QTimer
:
请注意,该对象的所有活动计时器都将被重置。计时器首先在当前线程中停止,然后在 targetThread 中重新启动(以相同的时间间隔)。因此,在线程之间不断移动对象可以无限期地推迟计时器事件。
结论:我认为你应该考虑使用你自己的QThread
来运行它的事件循环,并在那里创建你的 workerQObject
而不是使用QtConcurrent
. 这种方式比移动QObject
s 要好得多,并且可以避免使用当前方法可能出现的许多错误。查看Qt 中多线程技术的比较表,然后选择最适合您的用例的技术。仅QtConcurrent
在您只想执行一次调用函数并获取其返回值时使用。如果你想与线程进行永久交互,你应该切换到使用你自己的QThread
与 worker QObject
s。
Qt 会自动将 QObjects 移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效线程中?
不,Qt 不会自动移入QObject
父线程。
这种行为没有明确记录,所以我对 Qt 框架源代码master 分支做了一个小调查。
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
事件发布到 cleanup ,而不是清理和删除QObject::deleteLater
的 TLS 数据。之后将不会收到来自该线程的任何事件,但的线程亲和性保持不变。看到实现以了解线程亲和力如何变化 是很有趣的。QThreadStorageData::finish(tls_data)
eventDispatcher
QObject
QObject
void QObject::moveToThread(QThread *targetThread)
的实现void QThreadPrivate::finish(void *arg, bool lockAnyway)
清楚地表明QObject
' 的线程亲和性不会被QThread
.
虽然这是一个老问题,但我最近问了同样的问题,只是使用 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()
,那么您将收到一个错误,因为它会解析为所在的 QThread 对象anInstance
,而不是QtConcurrent 为它调度的线程。你会看到类似...
Cannot create children for a parent in another thread. Parent: QThread(xyz), parents thread: QThread(xyz), currentThread(abc)
希望我的测试对其他人有用。如果有人知道一种方法来获取指向 QtConcurrent 运行该方法的 QThread 的指针,我很想听听!
我不确定 Qt 是否会自动更改线程亲和力。但即使是这样,唯一合理的移动线程是主线程。我会自己将它们推到线程函数的末尾。
myObject->moveToThread(QApplication::instance()->thread());
现在,这仅在对象使用事件过程(如发送和接收信号)时才重要。
尽管 Qt 文档似乎没有指定您可以通过跟踪QObject::thread()
线程完成之前和之后返回的内容来发现的行为。