Подтвердить что ты не робот

Что происходит с близостью потока объекта QObject, созданного в рабочем потоке, который затем завершается?

Скажем, я вызываю QtConcurrent::run(), который запускает функцию в рабочем потоке, и в этой функции я динамически выделяю несколько QObjects (для последующего использования). Поскольку они были созданы в рабочем потоке, их сродство к потоку должно быть связано с рабочим потоком. Однако, как только рабочий поток завершается, сродство потока QObject больше не будет действительным.

Вопрос: автоматически ли Qt переносит QObjects в родительский поток или не несет ответственности за перенос их в допустимый поток до завершения рабочего цикла?

4b9b3361

Ответ 1

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

Напомним, что a QThread не является потоком, он управляет потоком.

Когда заканчивается QThread, он продолжает существовать, и объекты, которые в нем живут, продолжают жить в нем, но они больше не обрабатывают события. QThread можно перезапустить (не рекомендуется), после чего обработка событий будет возобновлена ​​(таким образом, тот же QThread мог бы управлять другим потоком).

Когда a QThread уничтожается, объекты, которые в нем живут, перестают иметь сходство нитей. Документация не гарантирует этого, и на самом деле говорит: "Вы должны убедиться, что все объекты, созданные в потоке, удалены, прежде чем удалить QThread".


Скажем, я вызываю QtConcurrent::run(), который запускает функцию в рабочем потоке, и в этой функции я динамически выделяю несколько QObjects (для последующего использования). Поскольку они были созданы в рабочем потоке, их сродство к потоку должно быть связано с рабочим потоком. Однако, как только рабочий поток завершается, сродство потока 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();

Вы можете вручную перенести объект из QThread, прежде чем он будет возвращен в QThreadPool, или просто не используйте QtConcurrent::run. Имея конструкцию задачи QtConcurrent::run QObject, которая переживает задачу, является сомнительной конструкцией, задачи должны быть автономными. Как отметил @Mike, QThread, используемый QtConcurrent::run, не имеет циклов событий.

Ответ 2

Однако, как только рабочий поток завершается, аффинность потока QObject больше не будет действительна.

Рабочий поток завершает работу НЕ после вызова функции. Весь смысл использования QtConcurrent::run выполняет большое количество небольших задач в глобальном пуле потоков (или в некоторых из них QThreadPool), а повторно использовать потоки, чтобы избежать накладных расходов на создание и уничтожение потоков для каждой из этих небольших задач. В дополнение к распределению вычислений по всем доступным ядрам.

Вы можете попробовать посмотреть исходный код для Qt, чтобы увидеть как QtConcurrent::run реализовано. Вы увидите, что он заканчивает вызов RunFunctionTaskBase::start, который по существу вызывает QThreadPool::start с QRunnable, который вызывает функцию, которая была первоначально отправлена ​​на QtConcurrent::run.

Теперь я хочу, чтобы QThreadPool::start был добавлен QRunnable в очередь, и затем пытается пробудить один из потоков из пула потоков (которые ждут для добавления нового QRunnable в очередь). Дело в том, что потоки из пула потоков не запускают цикл событий (они не предназначены для этого), они просто выполняют QRunnable в очереди и ничего больше (они реализованы по соображениям эффективности).

Это означает, что, когда вы создаете QObject в функции, выполняемой в QtConcurrent::run, вы просто создаете QObject, который живет в потоке без цикла событий, из docs, ограничения включают в себя:

Если цикл событий не запущен, события не будут доставлены объекту. Например, если вы создаете объект QTimer в потоке, но никогда не вызываете exec(), QTimer никогда не будет излучать его сигнал timeout(). Вызов deleteLater() тоже не будет работать. (Эти ограничения распространяются и на основной поток.) ​​


TL; DR: QtConcurrent::run запускает функции в потоках из глобального QThreadPool (или предоставленного). Эти потоки не запускают цикл событий, они просто ждут выполнения QRunnable. Таким образом, QObject, живущий в потоке из этих потоков, не получает никаких событий.


В документации, они поместили с помощью QThread (возможно, с циклом события и рабочим объектом) и используя QtConcurrent::run в качестве двух отдельных многопоточных технологий. Они не должны смешиваться. Таким образом, нет рабочих объектов в пулах потоков, это просто вызывает проблемы.

Вопрос: автоматически ли Qt переносит QObjects в родительский поток или не несет ответственности за перенос их в допустимый поток до завершения рабочего цикла?

Я думаю, что, посмотрев на вещи таким образом, ответ очевиден, что Qt делает НЕ перемещение QObject в любой поток автоматически. Документация предупреждает об использовании QObject в QThread без цикла события и что он.

Вы можете перемещать их в любой поток, который вам нравится. Но имейте в виду, что moveToThread() может иногда вызывать проблемы. Например, если перемещение вашего рабочего объекта связано с перемещением QTimer:

Обратите внимание, что все активные таймеры для объекта будут reset. Таймеры сначала останавливаются в текущем потоке и перезапускаются (с тем же интервалом) в targetThread. В результате постоянное перемещение объекта между потоками может откладывать события таймера неограниченно.


Заключение: Я думаю, что вам стоит подумать о том, чтобы использовать свой собственный QThread, который запускает цикл событий, и создайте вместо этого своего рабочего QObject вместо QtConcurrent. Этот способ намного лучше, чем перемещение QObject вокруг, и может избежать многих ошибок, которые могут возникнуть в результате использования вашего текущего подхода. Посмотрите сравнительную таблицу технологий многопоточности в Qt и выберите технологию, которая наилучшим образом соответствует вашему варианту использования. Используйте только QtConcurrent, если вы хотите просто выполнить функцию одного вызова и получить ее возвращаемое значение. Если вам нужно постоянное взаимодействие с потоком, вы должны переключиться на использование своего QThread с рабочим QObject s.

Ответ 3

Qt автоматически перемещает QObjects в родительский поток или не несет ответственности за перенос их в допустимый поток до завершения рабочего цикла?

Нет, 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, чем данные TLS, очищенные с помощью QThreadStorageData::finish(tls_data) и eventDispatcher удалены. После этого QObject не получит никаких событий из этого потока, но QObject сродство нитей остается неизменным. Интересно увидеть реализацию void QObject::moveToThread(QThread *targetThread), чтобы понять, как меняется аффинность потока.

Реализация void QThreadPrivate::finish(void *arg, bool lockAnyway) дает понять, что QObject сродство нитей не изменяется на QThread.

Ответ 4

Хотя это старый вопрос, я недавно задал тот же вопрос и просто ответил на него с помощью QT 4.8 и некоторых тестов.

AFAIK вы не можете создавать объекты с родителем из функции QtConcurrent:: run. Я пробовал следующие два пути. Позвольте мне определить блок кода, тогда мы рассмотрим поведение, выбрав POINTER_TO_THREAD.

Какой-нибудь код psuedo покажет вам мой тест

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)

Надеюсь, что мое тестирование будет полезным для кого-то другого. Если кто-то знает способ получить указатель на QThread, в котором QtConcurrent запускает метод, мне было бы интересно его услышать!

Ответ 5

Я не уверен, что Qt автоматически изменит аффинность потока. Но даже если это так, единственной разумной нитью для перехода является основной поток. Я бы сам нажал их в конце резьбовой функции.

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

Теперь это имеет значение только в том случае, если объекты используют процесс обработки событий, например сигналы отправки и получения.

Ответ 6

Хотя в документах Qt не указано поведение, которое вы могли бы обнаружить, отслеживая, что QObject::thread() возвращает до и после завершения потока.