26

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

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

4

6 回答 6

8

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,并且QObjects 生活在其中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没有事件循环。

于 2016-10-13T17:23:02.357 回答
5

但是,一旦工作线程终止,QObject 线程亲和性应该不再有效。

工作线程不会您的函数调用后终止。使用的重点QtConcurrent::run是在全局线程池(或提供的一些)上执行大量小任务,QThreadPool同时重用线程以避免为这些小任务中的每一个创建和销毁线程的开销。除了在所有可用内核上分布计算。

您可以尝试查看 Qt 的源代码以了解如何QtConcurrent::run实现。您将看到它最终RunFunctionTaskBase::start调用.QThreadPool::startQRunnableQtConcurrent::run

现在我想要达到的一点是,QThreadPool::start通过添加QRunnable到队列中,然后尝试从线程池中唤醒其中一个线程(正在等待将新线程QRunnable添加到队列中)来实现。这里要注意的是,线程池中的线程没有运行事件循环(它们不是设计为以这种方式运行),它们只是为了执行QRunnable队列中的 s 而仅此而已(它们是以这种方式实现的性能原因很明显)。

这意味着,当您QObject在执行的函数中创建 a 时QtConcurrent::run,您只是在创建一个QObject存在于没有事件循环的线程中的docs,限制包括:

如果没有事件循环正在运行,则不会将事件传递给对象。例如,如果您QTimer在线程中创建一个对象但从不调用exec()QTimer则永远不会发出它的timeout()信号。打电话deleteLater()也不行。(这些限制也适用于主线程。)


TL;DR: 在全局(或提供的)QtConcurrent::run线程中运行函数。QThreadPool这些线程不运行事件循环,它们只是等待QRunnables 运行。因此,QObject生活在来自这些线程的线程中不会获得任何事件传递。


文档中,他们将 using QThread(可能带有事件循环和工作对象)和 usingQtConcurrent::run作为两种独立的多线程技术。它们不应该混合在一起。所以,线程池中没有工作对象,这只是自找麻烦。

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

我认为在这样看之后,答案很明显,Qt 不会自动QObjects 移动到任何线程中。文档已经警告过QObjectQThread没有事件循环的情况下使用 a ,仅此而已。

您可以自由地将它们移动到您喜欢的任何线程。但请记住,这moveToThread()有时会导致问题。例如,如果移动工作对象涉及移动 a QTimer

请注意,该对象的所有活动计时器都将被重置。计时器首先在当前线程中停止,然后在 targetThread 中重新启动(以相同的时间间隔)。因此,在线程之间不断移动对象可以无限期地推迟计时器事件。


结论:我认为你应该考虑使用你自己的QThread来运行它的事件循环,并在那里创建你的 workerQObject而不是使用QtConcurrent. 这种方式比移动QObjects 要好得多,并且可以避免使用当前方法可能出现的许多错误。查看Qt 中多线程技术的比较表,然后选择最适合您的用例的技术。仅QtConcurrent在您只想执行一次调用函数并获取其返回值时使用。如果你想与线程进行永久交互,你应该切换到使用你自己的QThread与 worker QObjects。

于 2016-10-14T13:13:16.200 回答
3

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;
}

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事件发布到 cleanup ,而不是清理和删除QObject::deleteLater的 TLS 数据。之后将不会收到来自该线程的任何事件,但的线程亲和性保持不变。看到实现以了解线程亲和力如何变化 是很有趣的。QThreadStorageData::finish(tls_data)eventDispatcherQObjectQObjectvoid QObject::moveToThread(QThread *targetThread)

的实现void QThreadPrivate::finish(void *arg, bool lockAnyway)清楚地表明QObject' 的线程亲和性不会被QThread.

于 2016-10-14T07:44:54.157 回答
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(),那么您将收到一个错误,因为它会解析为所在的 QThread 对象anInstance,而不是QtConcurrent 为它调度的线程。你会看到类似...

Cannot create children for a parent in another thread. Parent: QThread(xyz), parents thread: QThread(xyz), currentThread(abc)

希望我的测试对其他人有用。如果有人知道一种方法来获取指向 QtConcurrent 运行该方法的 QThread 的指针,我很想听听!

于 2013-10-30T21:20:45.337 回答
1

我不确定 Qt 是否会自动更改线程亲和力。但即使是这样,唯一合理的移动线程是主线程。我会自己将它们推到线程函数的末尾。

myObject->moveToThread(QApplication::instance()->thread());

现在,这仅在对象使用事件过程(如发送和接收信号)时才重要。

于 2011-03-21T21:50:35.977 回答
1

尽管 Qt 文档似乎没有指定您可以通过跟踪QObject::thread()线程完成之前和之后返回的内容来发现的行为。

于 2012-01-21T23:40:30.490 回答