在工作线程中创建的QObject的线程亲和性会发生什么变化呢?

时间:2011-03-21 20:33:44

标签: c++ multithreading qt qtconcurrent

假设我调用QtConcurrent::run()在工作线程中运行一个函数,并在该函数中动态分配几个QObject(供以后使用)。由于它们是在工作线程中创建的,因此它们的线程关联应该是工作线程的线程关联。但是,一旦工作线程终止,QObject线程关联就不再有效了。

问题:Qt会自动将QObject移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效线程?

6 个答案:

答案 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::startimplemented,只需将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,主分支进行了一次小型调查。

QThreadQThreadPrivate::start开始:

unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg)
{

  ...

  thr->run();

  finish(arg);
  return 0;
}

QThread::terminate()实施:

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()返回的内容而找到的行为。