Tutorial d'initiation A la programmation avec l'API Windows
Tutorial d'initiation a la programmation Windows avec Microsoft Visual C++
Chapitre 5Le multithreading3. Partage des tâches
Cours théorique :
Avant d'utiliser des threads, il est nécessaire de bien comprendre comment le système gère ces threads. En effet, un programme multithread mal conçu peut conduire à une très forte baisse des performances système.
Il faut bien comprendre que si le système d'exploitation accorde à chaque thread un temps d'accès au processeur, le thread n'est pas obligé d'utiliser totalement ce temps. Le système accorde simplement un temps maximum d'utilisation. Au delà de cette période, le contrôle du processeur sera passé à un autre thread. Si le thread n'a plus aucune tâche à effectuer, il doit repasser lui même le contrôle au système. De cette manière, une application inactive ne monopolisera pas inutilement le processeur. Si une application inactive n'effectue pas ce passage, elle utilisera totalement son quota d'accès. Le système affichera alors une charge processeur de 100% et les performances seront fortement ralenties.
Le système d'exploitation dispose également d'un système de priorités permettant de gérer les quotas d'accès alloués aux processus. Voici un résumé rapide du fonctionnement de ce système de priorité :
Time Critical : ce thread requiert l'utilisation totale du processeur. Les quotas alloués à ce thread sont infinis. Si un tel thread ne repasse pas spontanément le contrôle au reste du système, le système restera en attente. Le contrôle du processeur ne sera accordé à aucun autre thread. Tout le système sera alors bloqué jusqu'à ce que le thread repasse le contrôle. Ce type d'application n'est en réalité quasi jamais utilisé. Il doit être réservé à des utilisations très particulières (ex : un gestionnaire de souris).
High : ce thread recevra le contrôle du processeur en priorité. Un tel thread ne peut pas être bloquant pour le système mais il pourra ralentir énormément les performances. Un thread utilisant cette priorité et effectuant un accès permanent au processeur causera un effondrement des performances générales du système. Cette priorité ne doit généralement être accordé qu'à des applications effectuant des tâches très brèves ou étant en premier plan. Les performances de ce thread ne seront quasiment pas affectées par l'exécution d'autres applications.
Normal : ce thread a une priorité normale. Il recevra le processeur exactement autant de fois que les autres threads de la même priorité. Si deux threads utilisent cette priorité et utilisent le processeur en permanence, les performances de chacun des thread seront divisées par 2. La quasi totalité des threads utilise cette priorité (c'est d'ailleurs la plus recommandée hors cas très particuliers).
Idle : ce thread recevra le contrôle du processeur seulement si aucune autre application ne demande le contrôle. Si ce thread effectue des tâches en continu et que toutes les autres applications sont inactives, il disposera de l'accès processeur dans sa quasi totalité. Si un autre thread effectue des tâches continues, ce thread n'aura plus aucun accès au processeur. Ce type de priorité peut être accordé à des applications effectuant des tâches de fond. De cette manière les performances du système resteront inchangées. La charge du processeur sera alors continuellement de 100%. Le partage sera alors : 0% pour le thread Idle si le système utilise le processeur. 100% pour le thread Idle si le système est inactif.
De manière générale il est inutile de modifier la priorité des threads. La modification des priorités ne doit être faite que dans les cas particuliers et en connaissance de cause. En particulier, les priorités 'Time Critical' et 'High' ne doivent jamais être utilisées abusivement.
De manière à conserver des performances système optimales, un thread doit repasser le contrôle au système dès qu'il devient inactif. Pour cela il dispose de plusieurs méthodes. Prenons l'exemple d'un thread qui désire attendre 150ms. Si ce thread effectue une boucle en testant l'heure système, il utilisera totalement son quota et le système affichera une charge de 100%. Si eu lieu de cela le thread effectue un appel à la fonction Sleep(), il repassera le contrôle au reste du système pour une durée de 150ms. Le contrôle du processeur ne lui sera plus accordé durant cette durée. La charge processeur sera alors de 0%. Le thread sera alors en attente. De cette manière les autres threads auront accès au processeur comme ci ce thread n'était pas présent et les performances système seront conservées.
Un autre exemple est la récupération des messages. La fonction GetMessage() repasse le contrôle au système dès que tous les messages ont été traités. Si l'application est inactive, le contrôle est donc repassé immédiatement au système et la charge processeur affichée est de 0%. Si un thread utilise PeekMessage() dans une boucle, le thread conservera l'accès au processeur même si aucun message n'est présent dans la file d'attente. La charge processeur sera donc de 100% même si l'application est inactive. Ce type d'erreurs ne doit donc surtout pas être commis sous peine de diminuer gravement les performances globales du système.
Il est également indispensable de comprendre qu'on ne peut jamais prévoir l'ordre dans lequel les threads recevront l'accès au processeur. Une application multithread devra toujours utiliser les fonctions de synchronisation (qui seront détaillées plus tard) et ne devra jamais tenter de prévoir l'ordre d'exécution des instructions. De plus, deux lignes consécutives d'une fonction ne seront pas forcément effectuées à la suite car le contrôle du processeur pourra être passé à d'autres threads entre temps. Il faut donc bien avoir en tête les contraintes imposées par un programme multithread avant de se lancer dans sa réalisation.
|