在工作线程中创建的 QObject 的线程亲和力会发生什么,然后该工作线程终止?

发布于 2024-10-24 20:04:00 字数 209 浏览 6 评论 0原文

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

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

Let's say I call QtConcurrent::run() which runs a function in a worker thread, and in that function I dynamically allocate several QObjects (for later use). Since they were created in the worker thread, their thread affinity should be that of the worker thread. However, once the worker thread terminates, the QObject thread affinity should no longer be valid.

The question: Does Qt automatically move the QObjects into the parent thread, or are we responsible in moving them to a valid thread before the worker thread terminates?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

阳光的暖冬 2024-10-31 20:04:00

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,并且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();

您可能希望在将对象返回到 QThreadPool 之前手动将其移出 QThread,或者只是不使用 QtConcurrent::run >。拥有一个比任务寿命更长的 QtConcurrent::run 任务构造 QObject 是一个有问题的设计,任务应该是独立的。正如 @Mike 所指出的,QtConcurrent::run 使用的 QThread 没有事件循环。

QThread is not documented to automatically move any QObjects when it finishes, so I think we can already conclude that it does no such thing. Such behavior would be very surprising, and at odds with the rest of the API.

Just for completeness, I tested with 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);

Recall that a QThread is not a thread, it manages a thread.

When a QThread finishes, it continues to exist, and the objects that live in it continue to live in it, but they no longer process events. The QThread can be restarted (not recommended), at which point event processing will resume (so the same QThread could then be managing a different thread).

When a QThread is destroyed, the objects that lived in it cease to have any thread affinity. The documentation doesn't guarantee this, and in fact says "You must ensure that all objects created in a thread are deleted before you delete the QThread."


Let's say I call QtConcurrent::run() which runs a function in a worker thread, and in that function I dynamically allocate several QObjects (for later use). Since they were created in the worker thread, their thread affinity should be that of the worker thread. However, once the worker thread terminates, the QObject thread affinity should no longer be valid.

The QThread does not terminate in this scenario. When a task spawned by QtConcurrent::run finishes, the QThread it was running in is returned to the QThreadPool and may be reused by a subsequent call to QtConcurrent::run, and QObjects living in that QThread continue to live there.

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();

You might want to manually move an object out of a QThread before it is returned to the QThreadPool, or just don't use QtConcurrent::run. Having a QtConcurrent::run task construct QObjects which outlive the task is a questionable design, tasks should be self-contained. As noted by @Mike, the QThreads used by QtConcurrent::run do not have event loops.

南城追梦 2024-10-31 20:04:00

但是,一旦工作线程终止,QObject 线程关联性就不再有效。

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

您可以尝试查看 Qt 的源代码以查看 QtConcurrent::run 是如何实现的。您将看到它最终调用 < code>RunFunctionTaskBase::start,它本质上调用 < code>QThreadPool::start 带有一个 QRunnable,它调用最初传递给 QtConcurrent::run 的函数。

现在我想要说的是, QThreadPool::start通过将 QRunnable 添加到队列,然后尝试从线程中唤醒其中一个线程来实现池(正在等待 将新的 QRunnable 添加到队列中)。这里需要注意的是,线程池中的线程没有运行事件循环(它们不是设计为以这种方式运行的),它们只是为了执行队列中的 QRunnable ,什么也不做更多(显然出于性能原因,它们以这种方式实现)。

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

如果没有事件循环正在运行,事件将不会传递到对象。例如,如果您在线程中创建 QTimer 对象但从未调用 exec(),则 QTimer 将永远不会发出其 超时() 信号。调用 deleteLater() 也不起作用。 (这些限制也适用于主线程。)


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


文档中,他们使用了< a href="https://doc.qt.io/qt-5/threads-technologies.html#qthread-low-level-api-with-optional-event-loops" rel="nofollow">QThread (可能带有事件循环和工作对象) 并使用 QtConcurrent::run 作为两种独立的多线程技术。它们不应该混合在一起。所以,线程池中没有工作对象,这只是自找麻烦。

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

我认为,以这种方式看待事情之后,答案很明显,Qt 不会自动将 QObject 移动到任何线程中。该文档警告了在没有事件循环的情况下在 QThread 中使用 QObject 的情况,仅此而已。

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

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


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

However, once the worker thread terminates, the QObject thread affinity should no longer be valid.

The worker thread does NOT terminate after your function call. The whole point of using QtConcurrent::run is executing a large number of small tasks on the global thread pool (or some provided QThreadPool) while re-using threads to avoid the overhead of creating and destroying threads for each one of these small tasks. In addition to distributing computation across all available cores.

You can try looking at the source code for Qt to see how QtConcurrent::run is implemented. You will see that it ends up calling RunFunctionTaskBase::start, which essentially calls QThreadPool::start with a QRunnable that calls the function that was passed initially to QtConcurrent::run.

Now the point that I want to get to is that, QThreadPool::start is implemented by adding the QRunnable to a queue, and then trying to wake up one of the threads from the thread pool (which are waiting for a new QRunnable to be added to the queue). The thing to note here, is that threads from the thread pool are not running an event loop (they are not designed to act this way), they are there just to execute QRunnables in the queue and nothing more (they are implemented this way for performance reasons obviously).

This means that, the moment you are creating a QObject in a function executed in QtConcurrent::run, you are just creating a QObject that lives in a thread with no event-loop, from the docs, restrictions include:

If no event loop is running, events won't be delivered to the object. For example, if you create a QTimer object in a thread but never call exec(), the QTimer will never emit its timeout() signal. Calling deleteLater() won't work either. (These restrictions apply to the main thread as well.)


TL;DR: QtConcurrent::run runs functions in threads from the global QThreadPool (or a provided one). Those threads do not run an event loop, They just wait for QRunnables to run. So, a QObject living in a thread from these threads doesn't get any events delivered.


In the documentation, They have put using QThread (possibly, with an event loop and a worker object) and using QtConcurrent::run as two separate multi-threading technologies. They are not meant to be mixed together. So, no worker objects in thread pools, this is just asking for trouble.

The question: Does Qt automatically move the QObjects into the parent thread, or are we responsible in moving them to a valid thread before the worker thread terminates?

I think that after looking at things this way, The answer is obvious that Qt does NOT move QObjects into any thread automatically. The documentation has warned about using a QObject in a QThread without an event loop, and that's it.

You are free to move them to whatever thread you like. But please keep in mind that moveToThread() can sometimes cause problems. For example, if moving your worker object involves moving a QTimer:

Note that all active timers for the object will be reset. The timers are first stopped in the current thread and restarted (with the same interval) in the targetThread. As a result, constantly moving an object between threads can postpone timer events indefinitely.


Conclusion: I think that you should consider using your own QThread that runs its event loop, and create your worker QObjects there instead of using QtConcurrent. This way is far better than moving QObjects around, and can avoid many errors that can arise from using your current approach. Have a look at the comparison table of multi-threading technologies in Qt and choose the technology that best suits your use case. Only use QtConcurrent if you want to just execute a one-call function and get its return value. If you want permanent interaction with the thread, you should switch to using your own QThread with worker QObjects.

悲凉≈ 2024-10-31 20:04:00

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

,Qt 不会自动将 QObject 移动到父线程中。

这种行为没有明确记录,所以我对 Qt 框架做了一个小调查 源代码,主分支。

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 事件用于清理 QObject::deleteLater,而不是使用 QThreadStorageData::finish(tls_data) 清理 TLS 数据并删除 eventDispatcher 。此后,QObject 将不会从该线程接收任何事件,但 QObject 的线程关联性保持不变。看到 的实现很有趣void QObject::moveToThread(QThread *targetThread) 了解线程关联性如何变化。

void QThreadPrivate::finish(void *arg, bool lockAnyway) 的实现清楚地表明 QObject 的线程关联性不会被 QThread 改变。

Does Qt automatically move the QObjects into the parent thread, or are we responsible in moving them to a valid thread before the worker thread terminates?

No, Qt doesn't automatically move QObject into the parent thread.

This behavior doesn't explicitly documented, so I've done a small investigation of the Qt framework source code, master branch.

QThread starts in QThreadPrivate::start:

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

  ...

  thr->run();

  finish(arg);
  return 0;
}

QThread::terminate() implementation:

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

In both cases thread finalization is done in 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;
}

It posts QEvent::DeferredDelete event to cleanup QObject::deleteLater, than TLS data cleaned up with QThreadStorageData::finish(tls_data) and eventDispatcher deleted. After that QObject will receive no events from this thread, but QObject's thread affinity stays the same. It's interesting to see implementation of void QObject::moveToThread(QThread *targetThread) to understand how thread affinity changes.

Implementation of void QThreadPrivate::finish(void *arg, bool lockAnyway) makes clear that QObject's thread affinity is not changed by QThread.

蓝礼 2024-10-31 20:04:00

虽然这是一个老问题,但我最近问了同样的问题,并只是使用 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 为其分派的线程。您将看到类似...

无法在另一个线程中为父级创建子级。父级:anInstance,父级线程:QThread(xyz), currentThread(abc)

如果POINTER_TO_THREAD设置为QObject::thread(),那么你会得到一个错误,因为它将解析为 anInstance 所在的 QThread 对象,而不是 QtConcurrent 为其分派的线程。您将看到类似...

无法在另一个线程中为父级创建子级。父线程:QThread(xyz),父线程:QThread(xyz),currentThread(abc)

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

Although this is an old question, I recently asked the same question, and just answered it using QT 4.8 and some testing.

AFAIK you cannot create objects with a parent from a QtConcurrent::run function. I have tried the following two ways. Let me define a code block then we will explore the behavior by selecting POINTER_TO_THREAD.

Some psuedo code will show you my test

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

Ignoring potential scoping issues...

If POINTER_TO_THREAD is set to this, then you will get an error because this will resolve to a pointer to the anInstance object which lives in the main thread, not the thread QtConcurrent has dispatched for it. You will see something like...

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

If POINTER_TO_THREAD is set to QObject::thread(), then you will get an error because because it will resolve to the QThread object in which anInstance lives, and not the thread QtConcurrent has dispatched for it. You will see something like...

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

Hope my testing is of use to someone else. If anyone knows a way to get a pointer to the QThread which QtConcurrent runs the method in, I would be interested to hear it!

一紙繁鸢 2024-10-31 20:04:00

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

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

现在,只有当对象使用发送和接收信号等事件过程时,这才重要。

I am not sure if Qt automatically change the thread affinity. But even if it does, the only reasonable thread to move to is the main thread. I would push them at the end of the threaded function myself.

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

Now this only matters if the objects make use of event process like send and receive signals.

飞烟轻若梦 2024-10-31 20:04:00

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

Although the Qt docs don't appear to specify the behaviour you could find out by keeping track of what QObject::thread() returns before and after the thread finishes.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文