繁体   English   中英

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

[英]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分支做了一个小小的调查。

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)实现表明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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM