Developpez.com - Qt
X

Choisissez d'abord la catégorieensuite la rubrique :

Viadeo Twitter Facebook Share on Google+   
Logo Documentation Qt ·  Page d'accueil  ·  Toutes les classes  ·  Toutes les fonctions  ·  Vues d'ensemble  · 

Les threads et les QObject

QThread hérite de QObject. Il émet des signaux pour indiquer que le thread a commencé ou a terminé son exécution, et fournit également quelques slots.

Introduction

Plus intéressant encore, les QObject peuvent être utilisés dans de multiples threads, émettre des signaux appelant des slots dans d'autres threads, et poster des évènements aux objets qui « vivent » dans d'autres threads. Cela est possible car chaque thread est autorisé à posséder sa propre boucle d'évènements.

Sujets :

La ré-entrance de QObject

QObject est ré-entrant(1). La plupart de ses sous-classes non GUI, telles que QTimer, QTcpSocket, QUdpSocket, QFtp et QProcess, sont aussi ré-entrantes, ce qui permet de les utiliser simultanément depuis plusieurs threads. Notez que ces classes sont conçues pour être générées et utilisées au sein d'un seul thread; le fonctionnement n'est pas garanti si l'on crée un objet dans un thread et que l'on appelle ses fonctions depuis un autre thread. Il y a trois contraintes à considérer :

  • L'enfant d'un QObject doit toujours être créé dans le thread où son parent a été créé. Cela implique, entre autres, que vous ne devriez jamais passer l'objet QThread (this) comme le parent d'un objet créé dans le thread (puisque que l'objet QThread a lui-même été créé dans un autre thread).
  • Les objets avec évènements ne peuvent être utilisés que dans un thread unique. En particulier, cela s'applique aux mécanismes de timers et au module de réseau. Par exemple, il est impossible de démarrer un timer ou de connecter une socket dans un thread qui n'est pas le thread de l'objet.
  • Il faut s'assurer que tous les objets qui ont été créés dans un thread sont bien supprimés avant de supprimer le QThread. La façon la plus simple de le faire est de créer les objets sur la pile dans l'implémentation de votre run().

Bien que QObject soit ré-entrant, les classes de GUI comme QWidget et toutes ses sous-classes ne sont pas ré-entrantes. Elles ne peuvent être utilisées qu'à partir du thread principal. Comme précisé précédemment, QCoreApplication::exec() doit aussi être appelée à partir de ce thread.

Il est donc impossible d'utiliser les classes de GUI dans d'autres threads que le thread principal. En pratique, cette impossibilité peut être contournée en plaçant les opérations coûteuses en temps de calcul dans un thread de travail séparé. Puis, à la clôture de ce thread, il suffit d'afficher les résultats à l'écran dans le thread principal. C'est l'approche utilisée pour implémenter les exemples Mandelbrot et Blocking Fortune Client.

Boucle d'évènements par thread

Chaque thread peut posséder sa propre boucle évènementielle. Le thread initial démarre sa boucle évènementielle en utilisant QCoreApplication::exec(); les autres threads peuvent démarrer une boucle évènementielle en utilisant QThread::exec(). Comme QCoreApplication, QThread fournit une fonction exit(int) et un slot quit().

Dans un thread, la présence d'une boucle évènementielle est indispensable pour utiliser certaines classes non-GUI (comme QTimer, QTcpSocket, et QProcess). Cela permet également de connecter des signaux à partir de n'importe quel thread aux slots d'un thread spécifique. Cela est expliqué plus en détail dans la section Les signaux et les slots à travers les threads ci-dessous.

image

On dit qu'une instance de QObject « vit » dans le thread dans lequel elle a été créée. Les évènements à destination de cet objet sont envoyés par la boucle évènementielle de ce thread. Le thread dans lequel vit un objet est disponible via QObject::thread().

Notez que QObject::thread() retourne zéro pour les QObjects créés avant QApplication. Cela signifie pour ces objets que le thread principal ne va gérer que les évènements postés; la propagation d'autres évènements n'est pas du tout effectuée pour les objets dépourvus de thread. Utilisez la fonction QObject::moveToThread() pour changer les liens d'un thread avec un objet et ses enfants (un objet ne peut pas être déplacé s'il a un parent).

Il n?est pas sûr d?appeler delete sur un QObject depuis un autre thread que le possesseur de l'objet (ou même d'accéder à l'objet par d'autres méthodes), à moins de pouvoir garantir que l'objet n'est pas en train de propager des évènements à cet instant précis. A la place, utilisez plutôt QObject::deleteLater(): un évènement DeferredDelete sera ainsi posté puis traité par la boucle évènementielle du thread quand elle aura la main. Par défaut, le thread possesseur d'un QObject est celui qui l'a créé. Mais ce n'est plus vrai si QObject::moveToThread() a été appelé après la création de l'objet.

Si aucune boucle évènementielle n'est lancée, les évènements ne seront pas distribués à l'objet. Par exemple, si vous créez un objet QTimer dans un thread mais que vous n'appelez jamais exec(), le QTimer n'émettra jamais son signal timeout(). L'appel de deleteLater() ne fonctionnera pas non plus. (Ces restrictions s'appliquent également au thread principal.)

Il est possible de poster manuellement des évènements dans n'importe quel thread, et à n'importe quel moment, en utilisant la fonction thread-safe QCoreApplication::postEvent(). Les évènements seront automatiquement envoyés par la boucle évènementielle du thread dans lequel l'objet a été créé.

Les filtres d'évènements sont utilisables dans tous les threads, à condition que l'objet de contrôle « vive » dans le même thread que l'objet contrôlé. De la même façon, QCoreApplication::sendEvent() est utilisable uniquement pour envoyer des évènements aux objets vivant dans le thread où la fonction est appelée. Cette restriction n'existe pas pour la fonction postEvent()).

Accès à des sous-classes de QObject depuis d'autres threads

QObject et toutes ses sous-classes ne sont pas thread-safe. Cela inclut la totalité du système d'envoi des évènements. Il est important de garder à l'esprit que la boucle évènementielle peut envoyer des évènements à votre sous-classe de QObject alors que vous êtes en train d'accéder à l'objet à partir d'un autre thread.

Si vous appelez une fonction d'une sous-classe de QObject qui ne vit pas dans le thread actuel et qu'il est possible que l'objet reçoive des évènements, alors vous devez protéger avec un mutex tous les accès aux données internes de votre sous-classe de QObject; dans le cas contraire, des crashs ou des comportements indésirables peuvent survenir.

Comme d'autres objets, les QThread vivent dans le thread dans lequel l'objet a été créé -- et non dans le thread qui est créé quand QThread::run() est appelé. Il n'est en général pas sûr de créer des slots dans votre sous-classe de QThread, à moins de protéger les variables membres avec un mutex.

D'un autre côté, vous pouvez émettre de façon sûre des signaux à partir d'une implémentation de QThread::run() car l'émission de signaux est thread-safe(2).

Les signaux et les slots à travers les threads

Qt supporte les types de connexions « signal-slot » suivants :

  • Auto Connection (défaut) Le comportement est identique à Direct Connection si l'émetteur et le récepteur sont dans le même thread. Le comportement est identique à Queued Connection si l'émetteur et le récepteur sont dans des threads différents.
  • Direct Connection Le slot est invoqué immédiatement quand le signal est émis. Le slot est exécuté dans le thread de l'émetteur, qui n'est pas nécessairement le thread du récepteur.
  • Queued Connection Le slot est invoqué quand le contrôle retourne à la boucle évènementielle du thread du récepteur. Le slot est exécuté dans le thread du récepteur.
  • Blocking Queued Connection Le slot est invoqué comme pour la Queued Connection, excepté que le thread courant est bloqué jusqu'à ce que le slot se termine. Note : Utiliser ce type pour connecter des objets dans le même thread causera un verrou mortel (deadlock).
  • Unique Connection Le comportement est le même que pour Auto Connection mais la connexion est faite seulement si cela ne duplique pas une connexion existante. C'est-à-dire, si le même signal est déjà connecté au même slot pour la même paire d'objets, alors la connexion n'est pas effectuée et connect() retourne false.

Le type de connexion peut être spécifié en passant un argument supplémentaire à connect(). Soyez attentifs au fait qu'utiliser des connexions directes quand l'émetteur et le récepteur vivent dans des threads différents n'est pas sûr si une boucle évènementielle est lancée dans le thread du récepteur. Cela n'est pas sûr pour la même raison qu'il n'est pas sûr d'appeler une fonction d'un objet vivant dans un autre thread.

QObject::connect() lui-même est thread-safe.

L'exemple Mandelbrot utilise une Queued Connection pour communiquer entre un thread de travail et le thread principal. Pour éviter de bloquer la boucle évènementielle du thread principal (et, par conséquent, l'interface utilisateur de l'application), tous les calculs des fractales de Mandelbrot sont effectués dans un thread de travail séparé. Le thread émet un signal lorsqu'il a terminé le rendu de la fractale.

De même, l'exemple Blocking Fortune Client utilise un thread séparé pour communiquer de façon asynchrone avec un serveur TCP.

Remerciements

Merci à Florent Renault pour la traduction et à Jonathan Courtois ainsi qu'à Philippe Beaucart pour leur relecture !


(1)Une fonction ou un objet est dit ré-entrant quand il peut être utilisé simultanément par plusieurs threads, en considérant son implémentation et son interface externe.
(2)Une fonction ou un objet est dit thread-safe quand il peut être utilisé simultanément par plusieurs threads, en considérant uniquement son implémentation.

Cette page est une traduction d'une page de la documentation de Qt, écrite par Nokia Corporation and/or its subsidiary(-ies). Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia. Qt 4.7
Copyright © 2018 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon, vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vous avez déniché une erreur, une redirection cassée ou tout autre problème, quel qu'il soit ? Ou bien vous désirez participer à ce projet de traduction ? N'hésitez pas à nous contacter ou par MP !
Responsable bénévole de la rubrique Qt : Thibaut Cuvelier -