![](/img/trans.png)
[英]What is the QThreadData of a QObject which is created within std::thread or pthread_create
[英]What happens to the thread affinity of a QObject created within a worker thread which then terminates?
假设我调用QtConcurrent::run()
在一个工作线程中运行一个函数,在该函数中我动态分配几个QObject(供以后使用)。 由于它们是在工作线程中创建的,因此它们的线程关联应该是工作线程的线程关联。 但是,一旦工作线程终止,QObject线程关联就不再有效。
问题:Qt是自动将QObjects移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效线程?
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
被销毁时,生活在其中的对象不再具有任何线程关联。 文档并不能保证这一点,事实上说: “在删除QThread
之前,必须确保删除线程中创建的所有对象。”
假设我调用
QtConcurrent::run()
在一个工作线程中运行一个函数,在该函数中我动态分配几个QObject(供以后使用)。 由于它们是在工作线程中创建的,因此它们的线程关联应该是工作线程的线程关联。 但是,一旦工作线程终止,QObject线程关联就不再有效。
QThread
不会在此方案中终止。 当QtConcurrent::run
生成的任务完成时,它运行的QThread
返回到QThreadPool
,可以通过后续调用QtConcurrent::run
重用,生活在QThread
QObject
继续存在。
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();
您可能希望在将对象返回到QThreadPool
之前手动将对象移出QThread
,或者只是不使用QtConcurrent::run
。 拥有一个QtConcurrent::run
任务构造QObject
,它比任务更长是一个有问题的设计,任务应该是自包含的。 正如@Mike所指出的, QtConcurrent::run
使用的QThread
没有事件循环。
但是,一旦工作线程终止,QObject线程关联就不再有效。
工作线程的函数调用后没有终止。 使用QtConcurrent::run
是在全局线程池(或一些提供的QThreadPool
)上执行大量小任务,同时重用线程以避免为每个小任务创建和销毁线程的开销。 除了在所有可用内核之间分配计算。
你可以尝试查看Qt的源代码,看看如何实现QtConcurrent::run
。 您将看到它最终调用RunFunctionTaskBase::start
,它实际上调用了QThreadPool::start
,其中QRunnable
调用最初传递给QtConcurrent::run
的函数。
现在,我想的一点是, QThreadPool::start
被实施通过将QRunnable
到一个队列,然后试图唤醒线程池中线程(这是一个等待新QRunnable
是添加到队列中)。 这里需要注意的是,来自线程池的线程没有运行事件循环(它们不是按照这种方式设计的),它们只是在队列中执行QRunnable
而已经没有了(它们以这种方式实现)出于性能原因,显然)。
这意味着,当您在QtConcurrent::run
执行的函数中创建QObject
时,您只是创建一个QObject
,它位于一个没有事件循环的线程中,来自文档 ,限制包括:
如果没有运行事件循环,则不会将事件传递给对象。 例如,如果在线程中创建
QTimer
对象但从不调用exec()
,则QTimer
将永远不会发出其timeout()
信号。 调用deleteLater()
也不起作用。 (这些限制也适用于主线程。)
TL; DR: QtConcurrent::run
在全局QThreadPool
(或提供的)的线程中运行函数。 这些线程不运行事件循环,它们只是等待QRunnable
运行。 因此,生活在来自这些线程的线程中的QObject
不会传递任何事件。
在文档中 ,他们使用QThread
(可能使用事件循环和worker对象)并使用QtConcurrent::run
作为两个独立的多线程技术。 它们并不意味着混合在一起。 因此, 线程池中没有工作对象 ,这只是在寻找麻烦。
问题:Qt是自动将QObjects移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效线程?
我认为看事情的这种方式后,答案是显而易见的是Qt 不动QObject
s转换任何线程自动。 该文档已经警告过在没有事件循环的情况下在QThread
使用QObject
,就是这样。
您可以随意将它们移动到您喜欢的任何线程。 但请记住, moveToThread()
有时会导致问题。 例如,如果移动工作对象涉及移动QTimer
:
请注意,将重置该对象的所有活动计时器。 计时器首先在当前线程中停止,然后在targetThread中重新启动(具有相同的间隔)。 因此,在线程之间不断移动对象可以无限期地推迟计时器事件。
结论:我认为您应该考虑使用自己运行其事件循环的QThread
,并在那里创建工作者QObject
而不是使用QtConcurrent
。 这种方式远比移动QObject
更好,并且可以避免使用当前方法可能产生的许多错误。 查看Qt中多线程技术的比较表,并选择最适合您的用例的技术。 如果您只想执行一次调用函数并获取其返回值,则仅使用QtConcurrent
。 如果你想与线程永久交互,你应该切换到使用你自己的QThread
和worker QObject
。
Qt会自动将QObject移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效线程?
不 ,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
事件发布到清理QObject::deleteLater
,而不是使用QThreadStorageData::finish(tls_data)
和eventDispatcher
删除的TLS数据。 之后, QObject
将不会从此线程接收任何事件,但QObject
的线程关联性保持不变。 看到void QObject::moveToThread(QThread *targetThread)
以了解线程关联性如何变化是很有趣的。
void QThreadPrivate::finish(void *arg, bool lockAnyway)
实现表明QThread
没有改变QObject
的线程亲和性。
虽然这是一个老问题,但我最近问了同样的问题,并且刚刚使用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的指针,我会很有兴趣听到它!
我不确定Qt是否会自动更改线程关联。 但即使它确实如此,唯一合理的线程就是主线程。 我会在自己的线程函数结束时推送它们。
myObject->moveToThread(QApplication::instance()->thread());
现在这只有在对象使用发送和接收信号等事件过程时才有意义。
虽然Qt文档似乎没有指定您可以通过跟踪QObject::thread()
在线程完成之前和之后返回的内容而找到的行为。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.