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

醉酒当歌 提交于 2020-01-12 03:08:06

问题


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?


回答1:


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.




回答2:


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.




回答3:


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.




回答4:


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!




回答5:


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.




回答6:


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.



来源:https://stackoverflow.com/questions/5383296/what-happens-to-the-thread-affinity-of-a-qobject-created-within-a-worker-thread

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!