简体   繁体   English

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

[英]What happens to the thread affinity of a QObject created within a worker thread which then terminates?

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). 假设我调用QtConcurrent::run()在一个工作线程中运行一个函数,在该函数中我动态分配几个QObject(供以后使用)。 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. 但是,一旦工作线程终止,QObject线程关联就不再有效。

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? 问题:Qt是自动将QObjects移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效线程?

QThread is not documented to automatically move any QObject s when it finishes, so I think we can already conclude that it does no such thing. QThread没有记录在它完成时自动移动任何QObject ,所以我认为我们已经可以得出结论它没有这样的事情。 Such behavior would be very surprising, and at odds with the rest of the API. 这种行为会非常令人惊讶,与API的其他部分不一致。

Just for completeness, I tested with Qt 5.6: 为了完整起见,我测试了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. 回想一下, QThread不是一个线程,它管理一个线程。

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. QThread完成后,它将继续存在,并且其中的对象继续存在于其中,但它们不再处理事件。 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). 可以重新启动QThread (不推荐),此时事件处理将恢复(因此相同的QThread可以管理不同的线程)。

When a QThread is destroyed, the objects that lived in it cease to have any thread affinity. QThread被销毁时,生活在其中的对象不再具有任何线程关联。 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 ." 文档并不能保证这一点,事实上说: “在删除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). 假设我调用QtConcurrent::run()在一个工作线程中运行一个函数,在该函数中我动态分配几个QObject(供以后使用)。 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. 但是,一旦工作线程终止,QObject线程关联就不再有效。

The QThread does not terminate in this scenario. QThread不会在此方案中终止。 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 QObject s living in that QThread continue to live there. 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();

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 . 您可能希望在将对象返回到QThreadPool之前手动将对象移出QThread ,或者只是不使用QtConcurrent::run Having a QtConcurrent::run task construct QObject s which outlive the task is a questionable design, tasks should be self-contained. 拥有一个QtConcurrent::run任务构造QObject ,它比任务更长是一个有问题的设计,任务应该是自包含的。 As noted by @Mike, the QThread s used by QtConcurrent::run do not have event loops. 正如@Mike所指出的, QtConcurrent::run使用的QThread没有事件循环。

However, once the worker thread terminates, the QObject thread affinity should no longer be valid. 但是,一旦工作线程终止,QObject线程关联就不再有效。

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. 使用QtConcurrent::run是在全局线程池(或一些提供的QThreadPool )上执行大量小任务,同时重用线程以避免为每个小任务创建和销毁线程的开销。 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 . 你可以尝试查看Qt的源代码,看看如何实现QtConcurrent::run 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 . 您将看到它最终调用RunFunctionTaskBase::start ,它实际上调用了QThreadPool::start ,其中QRunnable调用最初传递给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). 现在,我想的一点是, QThreadPool::start实施通过将QRunnable到一个队列,然后试图唤醒线程池中线程(这是一个等待QRunnable是添加到队列中)。 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 QRunnable s in the queue and nothing more (they are implemented this way for performance reasons obviously). 这里需要注意的是,来自线程池的线程没有运行事件循环(它们不是按照这种方式设计的),它们只是在队列中执行QRunnable而已经没有了(它们以这种方式实现)出于性能原因,显然)。

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: 这意味着,当您在QtConcurrent::run执行的函数中创建QObject时,您只是创建一个QObject ,它位于一个没有事件循环的线程中,来自文档 ,限制包括:

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. 例如,如果在线程中创建QTimer对象但从不调用exec() ,则QTimer将永远不会发出其timeout()信号。 Calling deleteLater() won't work either. 调用deleteLater()也不起作用。 (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). TL; DR: QtConcurrent::run在全局QThreadPool (或提供的)的线程中运行函数。 Those threads do not run an event loop, They just wait for QRunnable s to run. 这些线程不运行事件循环,它们只是等待QRunnable运行。 So, a QObject living in a thread from these threads doesn't get any events delivered. 因此,生活在来自这些线程的线程中的QObject不会传递任何事件。


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. 文档中 ,他们使用QThread (可能使用事件循环和worker对象)并使用QtConcurrent::run作为两个独立的多线程技术。 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? 问题:Qt是自动将QObjects移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效线程?

I think that after looking at things this way, The answer is obvious that Qt does NOT move QObject s into any thread automatically. 我认为看事情的这种方式后,答案是显而易见的是Qt QObject s转换任何线程自动。 The documentation has warned about using a QObject in a QThread without an event loop, and that's it. 该文档已经警告过在没有事件循环的情况下在QThread使用QObject ,就是这样。

You are free to move them to whatever thread you like. 您可以随意将它们移动到您喜欢的任何线程。 But please keep in mind that moveToThread() can sometimes cause problems. 但请记住, moveToThread()有时会导致问题。 For example, if moving your worker object involves moving a QTimer : 例如,如果移动工作对象涉及移动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. 计时器首先在当前线程中停止,然后在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 QObject s there instead of using QtConcurrent . 结论:我认为您应该考虑使用自己运行其事件循环的QThread ,并在那里创建工作者QObject而不是使用QtConcurrent This way is far better than moving QObject s around, and can avoid many errors that can arise from using your current approach. 这种方式远比移动QObject更好,并且可以避免使用当前方法可能产生的许多错误。 Have a look at the comparison table of multi-threading technologies in Qt and choose the technology that best suits your use case. 查看Qt中多线程技术比较表,并选择最适合您的用例的技术。 Only use QtConcurrent if you want to just execute a one-call function and get its return value. 如果您只想执行一次调用函数并获取其返回值,则仅使用QtConcurrent If you want permanent interaction with the thread, you should switch to using your own QThread with worker QObject s. 如果你想与线程永久交互,你应该切换到使用你自己的QThread和worker QObject

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? Qt会自动将QObject移动到父线程中,还是我们负责在工作线程终止之前将它们移动到有效线程?

No , Qt doesn't automatically move QObject into the parent thread. ,Qt不会自动将QObject移动到父线程中。

This behavior doesn't explicitly documented, so I've done a small investigation of the Qt framework source code , master branch. 这种行为没有明确记录,所以我对Qt框架源代码 ,master分支做了一个小小的调查。

QThread starts in QThreadPrivate::start : QThreadQThreadPrivate::start

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

  ...

  thr->run();

  finish(arg);
  return 0;
}

QThread::terminate() implementation: 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);
}

In both cases thread finalization is done in QThreadPrivate::finish : 在这两种情况下,线程终结都在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. 它将QEvent::DeferredDelete事件发布到清理QObject::deleteLater ,而不是使用QThreadStorageData::finish(tls_data)eventDispatcher删除的TLS数据。 After that QObject will receive no events from this thread, but QObject 's thread affinity stays the same. 之后, QObject将不会从此线程接收任何事件,但QObject的线程关联性保持不变。 It's interesting to see implementation of void QObject::moveToThread(QThread *targetThread) to understand how thread affinity changes. 看到void QObject::moveToThread(QThread *targetThread)以了解线程关联性如何变化是很有趣的。

Implementation of void QThreadPrivate::finish(void *arg, bool lockAnyway) makes clear that QObject 's thread affinity is not changed by QThread . void QThreadPrivate::finish(void *arg, bool lockAnyway)实现表明QThread没有改变QObject的线程亲和性。

Although this is an old question, I recently asked the same question, and just answered it using QT 4.8 and some testing. 虽然这是一个老问题,但我最近问了同样的问题,并且刚刚使用QT 4.8和一些测试来回答它。

AFAIK you cannot create objects with a parent from a QtConcurrent::run function. AFAIK您无法使用QtConcurrent :: run函数中的父项创建对象。 I have tried the following two ways. 我尝试了以下两种方法。 Let me define a code block then we will explore the behavior by selecting POINTER_TO_THREAD. 让我定义一个代码块,然后我们将通过选择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. 如果将POINTER_TO_THREAD设置为this ,则会出现错误,因为this将解析为指向生成在主线程中的anInstance对象的指针, 而不是 QtConcurrent为其调度的线程。 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. 如果POINTER_TO_THREAD设置为QObject::thread() ,那么您将收到一个错误,因为它将解析为anInstance所在的QThread对象,而不是 QtConcurrent为其调度的线程。 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! 如果有人知道如何获得QtConcurrent运行方法的QThread的指针,我会很有兴趣听到它!

I am not sure if Qt automatically change the thread affinity. 我不确定Qt是否会自动更改线程关联。 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. 现在这只有在对象使用发送和接收信号等事件过程时才有意义。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在std :: thread或pthread_create中创建的QObject的QThreadData是什么? - What is the QThreadData of a QObject which is created within std::thread or pthread_create QObject 的线程亲和性作为已删除的 QThread 是否安全? - Is it safe for a QObject's thread affinity to be a deleted QThread? 关于QThread,QObject,线程亲和力和事件循环的困惑 - Confusion regarding QThread, QObject, Thread Affinity and Event Loop 线程亲和力 - Thread Affinity 线程关联掩码对当前线程有什么好处? - What good are thread affinity mask changes for the current thread? QObject重入和线程安全 - QObject reentrancy and thread safety 线程池和数据关联 - Thread pool and data affinity 当最后一个线程终止时,是否有任何pthread函数调用某些内容? - Is there any pthread function which calls something when the last thread terminates? `moveToThread(nullptr)` 是从源线程拉出目标线程中的 QObject 的有效方法吗? - Is `moveToThread(nullptr)` a valid way to pull a QObject within the destination thread from its source thread? 当在后台线程上创建的 Dispatcher 没有关闭时会发生什么? 如何确保调度员正确关闭? - What happens when a Dispatcher created on a background thread is not shutdown? How to make sure that a dispatcher is properly shut down?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM