Qu'arrive-t-il à l'affinité d'un QObject créé dans un thread worker qui se termine alors?

disons que j'appelle QtConcurrent::run() qui exécute une fonction dans un thread worker, et dans cette fonction j'alloue dynamiquement plusieurs QObjects (pour une utilisation ultérieure). Puisqu'ils ont été créés dans le fil ouvrier, leur affinité de fil doit être celle du fil ouvrier. Cependant, une fois le thread worker terminé, L'affinité du thread QObject ne devrait plus être valide.

la question: Est-ce que Qt déplace automatiquement les QObjects dans le thread parent, ou sont nous responsable dans les déplacer vers un fil valide avant que le fil ouvrier se termine?

22
demandé sur Alexander Kondratskiy 2011-03-21 23:33:44

6 réponses

QThread n'est pas documentée pour déplacer automatiquement tout QObjects quand il se termine, donc je pense que nous pouvons déjà conclure qu'il n'en est rien. Un tel comportement serait très surprenant, et en contradiction avec le reste de l'API.

juste pour être complet, j'ai testé avec 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);

Rappeler que l' QThread n'est pas un thread, il gère un fil.

Quand QThread finitions, elle continue d'exister, et les objets qui vivent en elle continuer à vivre, mais ils ne traitent plus les événements. QThread peut être redémarré( non recommandé), à quel moment le traitement de l'événement reprendra (donc le même QThread pourrait alors gérer un thread différent).

Quand QThread est détruit, les objets qui y vivait plus l'affinité de thread. documentation ne garantit pas ça, et en fait, dit "vous devez Vous assurer que tous les objets créés dans un thread sont supprimés avant vous supprimez l' QThread."


disons que j'appelle QtConcurrent::run() qui exécute une fonction dans un thread worker, et dans cette fonction j'alloue dynamiquement plusieurs QObjects (pour une utilisation ultérieure). Puisqu'ils ont été créés dans le fil ouvrier, leur affinité de fil doit être celle du fil ouvrier. Cependant, une fois le thread worker terminé, L'affinité du thread QObject ne devrait plus être valide.

QThread ne met pas fin à ce scénario. Lorsqu'une tâche est engendrée par QtConcurrent::run finitions, le QThread il était en cours d'exécution en est retourné à l' QThreadPool et peut être réutilisé par un appel ultérieur à QtConcurrent::run et QObjects de vivre dans ce QThread continuer à y vivre.

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

Vous pourriez déplacer manuellement un objet en dehors d'un QThread avant de revenir à QThreadPool, ou tout simplement ne pas utiliser QtConcurrent::run. Avoir un QtConcurrent::run construction des tâches QObject s qui survivent la tâche est une conception discutable, les tâches devraient être autonome. Comme l'a noté @Mike, le QThreads utilisé par QtConcurrent::run n'ont pas de boucle d'événement.

6
répondu Oktalist 2016-10-14 14:55:44

cependant, une fois que le thread worker cesse, L'affinité du thread QObject ne devrait plus être valide.

le worker thread fait terminer après votre appel de fonction. Le point de l'ensemble de l'aide QtConcurrent::run exécute un grand nombre de petites tâches sur le pool global thread (ou certaines fournies QThreadPool) tandis que re-à l'aide de threads pour éviter la création et la destruction de fils pour chacun des ces petites tâches. En plus de distribuer le calcul dans tous les noyaux disponibles.

Vous pouvez essayer de regarder le code source de Qt pour voir comment QtConcurrent::run est mis en œuvre. Vous verrez qu'il finit par appeler RunFunctionTaskBase::start, qui appelle essentiellement QThreadPool::start avec un QRunnable qui appelle la fonction qui a été passé initialement QtConcurrent::run.

Maintenant, le point que je veux faire, c'est que, QThreadPool::start est mise en œuvre en ajoutant l' QRunnable à une file d'attente, et ensuite essayer de réveiller un des threads du pool de threads (qui sont attente pour un nouveau QRunnable pour être ajouté à la file d'attente). La chose à noter ici, c'est que les threads du pool de threads ne tournent pas une boucle d'événement (ils ne sont pas conçus pour agir de cette façon), ils sont là juste pour exécuter QRunnable s dans la file d'attente et rien de plus (ils sont mis en œuvre de cette façon pour des raisons de performance évidemment.)

Cela signifie que, au moment où vous créez un QObject dans une fonction exécutée dans QtConcurrent::run, vous êtes en création QObject qui vit dans un thread avec aucun cas, en boucle à partir de la docs, les restrictions comprennent:

si aucune boucle d'événement ne s'exécute, les événements ne seront pas livrés à l'objet. Par exemple, si vous créez un QTimer objet dans un thread, mais jamais appeler exec(), le QTimer n'émettra jamais ses timeout() signal. Appel de deleteLater() ne fonctionne pas, soit. (Ces restrictions s'appliquent également au fil principal.)


TL; DR:QtConcurrent::run exécute les fonctions dans les threads à partir du global QThreadPool (ou un autre). Ces threads ne font pas tourner une boucle d'événement, ils attendent juste QRunnables à courir. Donc, un QObject vivre dans un fil à partir de ces fils ne reçoit aucun événement livré.


Dans le documentation, Ils ont put à l'aide de QThread (possiblement, avec une boucle d'événement et un objet worker) et en utilisant QtConcurrent::run comme deux technologies multi-threading séparées. Ils ne sont pas faits pour être mélangés. Donc, pas d'objets worker dans les pools de thread, c'est juste d'avoir des ennuis.

la question: Qt déplace-t-il automatiquement les QObjects dans le thread parent, ou sommes-nous responsables de les déplacer vers un thread valide avant le thread worker se termine?

je pense qu'après avoir vu les choses de cette façon, la réponse est évidente que Qt fait déplacer QObjects dans n'importe quel thread automatiquement. La documentation a mis en garde contre l'utilisation d'un QObject dans un QThread sans boucle d'événement, et c'est tout.

vous êtes libre de les déplacer vers n'importe quel fil que vous aimez. Mais s'il vous plaît gardez à l'esprit que moveToThread() peut parfois causer des problèmes. Par exemple, si vous déménagez votre travailleur l'objet implique de déplacer un QTimer:

notez que tous les minuteurs actifs de l'objet seront réinitialisés. Les minuteries sont d'abord arrêtées dans le thread courant et redémarrées (avec le même intervalle) dans le targetThread. En conséquence, le déplacement constant d'un objet entre les threads peut retarder indéfiniment les événements de minuterie.


Conclusion: je pense que vous devriez considérer l'utilisation de votre propre QThread qui exécute sa boucle d'événements, et créez votre travailleur QObject S là au lieu d'utiliser QtConcurrent. De cette façon est beaucoup mieux que de déplacer QObjects autour, et peut éviter de nombreuses erreurs qui peuvent résulter de l'utilisation de votre approche. Jetez un oeil à l' tableau comparatif des technologies multi-threading en Qt et choisir la technologie qui convient le mieux à votre cas d'utilisation. Utilisez seulement QtConcurrent si vous souhaitez exécuter une fonction d'appel et obtenez sa valeur de retour. Si vous voulez une interaction permanente avec le fil, vous devez commutateur à l'aide de votre propre QThread avec le travailleur QObject S.

4
répondu Mike 2016-10-14 17:50:17

est-ce que Qt déplace automatiquement les QObjects dans le thread parent, ou est-ce que nous sommes responsables de les déplacer vers un thread valide avant que le thread worker se termine?

Aucun, Qt ne pas déplacer automatiquement QObject dans le fil parent.

ce comportement n'est pas explicitement documenté, donc j'ai fait une petite enquête sur le cadre Qt code source, branche master.

QThread commence dans QThreadPrivate::start:

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

  ...

  thr->run();

  finish(arg);
  return 0;
}

QThread::terminate() mise en oeuvre:

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

dans les deux cas, la finalisation du thread se fait en 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;
}

posts QEvent::DeferredDelete événement pour le nettoyage QObject::deleteLater, que les données TLS nettoyées avec QThreadStorageData::finish(tls_data) et eventDispatcher supprimé. Après que QObject ne recevra aucun événement de ce thread, mais n'est pas changée par QThread.

3
répondu Nikita 2016-10-14 07:44:54

bien qu'il s'agisse d'une vieille question, j'ai récemment posé la même question, et j'ai juste répondu en utilisant QT 4.8 et quelques tests.

AFAIK vous ne pouvez pas créer d'objets avec un parent à partir d'une fonction QtConcurrent::run. J'ai essayé les deux façons suivantes. Permettez-moi de définir un bloc de code puis nous allons explorer le comportement en sélectionnant POINTER_TO_THREAD.

Certains pseudo code va vous montrer mon 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)
}

ignorer la portée potentielle question...

Si POINTER_TO_THREAD est réglé sur this, vous obtiendrez une erreur car this résoudra à un pointeur vers le anInstance objet qui vit dans le thread principal, le thread QtConcurrent a été envoyé pour lui. Vous verrez quelque chose comme...

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

Si POINTER_TO_THREAD est réglé sur QObject::thread(), alors vous obtiendrez une erreur parce que parce qu'elle se résoudra à L'objet QThread dans lequel anInstance vie, et le thread QtConcurrent a été envoyé pour lui. Vous verrez quelque chose comme...

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

J'espère que mes tests sont utiles à quelqu'un d'autre. Si quelqu'un connaît un moyen pour obtenir un pointeur vers le QThread qui QtConcurrent exécute la méthode, je serais intéressé à entendre!

2
répondu user2025983 2013-10-30 21:20:45

Je ne suis pas sûr que Qt change automatiquement l'affinité du thread. Mais même si c'est le cas, le seul fil raisonnable vers lequel se déplacer est le fil principal. Je les pousserais moi-même à la fin de la fonction filetée.

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

cela n'a D'importance que si les objets utilisent un processus d'événement comme envoyer et recevoir des signaux.

1
répondu Stephen Chu 2011-03-21 21:50:35

bien que les docs Qt ne semblent pas spécifier le comportement que vous pourriez découvrir en gardant une trace de ce que QObject::thread() retourne avant et après la fin du fil.

1
répondu Troubadour 2012-01-21 23:40:30