Exécuter plusieurs tâches simultanément apparaît comme une source de gain indéniable lors de l’exécution de programmes. En la matière, Python propose une solution appelée le « threading ». Or, celle-ci est plus complexe qu’il n’y paraît au premier abord et sa maîtrise nécessite une analyse approfondie des fonctions attendues d’un programme. Qui plus est, le mécanisme des « threads » de Python n’aboutit pas à un parallélisme effectif des divers processus. Les explications suivent.
Imaginons que vous avez une série de tâches à accomplir :
- Rédiger un exposé en vue d’une prochaine réunion.
- Appeler trois prospects.
- Rédiger des devis.
Vous pouvez réaliser ces tâches une à une. Toutefois, vous pourriez aussi vous appuyer sur votre équipe et répartir ces activités de façon à ce qu’elles soient accomplies en parallèle. Il va de soi que l’ensemble sera réalisé plus rapidement.
Dans la programmation avec Python, le « threading » fonctionne selon une même logique. Au lieu d’exécuter une seule suite d’instructions, un programme peut gérer plusieurs « threads », des processus indépendants qui vont fonctionner plus ou moins en parallèle. Habituellement, il en résulte un programme globalement bien plus efficace.
En réalité, tout n’est pas si simple, car il peut exister des problèmes de coordination. Prenons l’exemple donné plus haut. Un point de détail de l’un des devis pourrait dépendre en partie de la conversation avec un des prospects. Donc, ledit devis ne pourra pas être complété tant que la conversation n’est pas achevée. De pareilles situations vont être rencontrées en informatique. Il sera donc nécessaire de bien organiser les threads et de coordonner leur progression.
Ainsi donc, le threading Python nécessite une analyse en amont afin d’isoler des tâches idéalement indépendantes les unes des autres et à défaut, de coordonner leur exécution dans le temps.
Qu’est ce qu’un thread ?
Un thread est un flux d’exécution séparé au sein d’un programme.
Par défaut, lorsque vous lancez un script Python, vous démarrez une instance de l’interpréteur Python. Votre code est donc habituellement exécuté dans un seul thread.
Toutefois, il est possible de concevoir un programme Python de façon à ce qu’il effectue certaines tâches de manière concurrente. Dans ce contexte, nous aurons affaire à plusieurs threads.
Un processus Python exploitant le « threading » se terminera une fois que tous les threads auront été achevés.
Dans quelles circonstances mettre à profit le threading ?
Voici quelques exemples de situations dans lesquelles le threading serait bienvenu.
- Une application requiert des données indépendantes provenant de plusieurs sites Web. Sans threading, elle doit attendre la fin du téléchargement depuis un site avant de démarrer le suivant. Grâce au threading, elle peut lancer ces téléchargements en même temps – dès qu’un temps d’attente est constaté sur l’un, elle donne la main à un autre.
- Une application est conçue de façon à opérer une sauvegarde régulière des informations entrées par l’utilisateur. Si l’on n’exploite pas le threading, ladite application va sembler se « geler » au moment de l’enregistrement des données. Si la sauvegarde est opérée par un thread séparé, l’application va continuer de réagir aux entrées de l’utilisateur durant la sauvegarde.
- À partir du moment où un usager a entré certaines informations telles que les paramètres d’un prêt potentiel, une application doit opérer une analyse des données complexe, nécessitant un temps de calcul important. En déléguant ces opérations à un thread séparé, l’application va continuer d’accepter les interactions de l’utilisateur.
Les avantages du threading
Amélioration de la performance
Le temps global d’exécution d’un programme est accéléré et ce plus particulièrement, lorsqu’un programme requiert des attentes potentielles comme lors de la lecture d’un fichier et de requêtes sur le Web.
Meilleure réponse de l'application
L’application peut continuer à répondre aux demandes de l’utilisateur (comme les clics de souris) tout en effectuant d’autres tâches en arrière-plan.
Meilleure gestion des situations de blocage
Les opérations susceptibles de bloquer l’exécution du code (comme l’attente d’une réponse du réseau) n’amènent pas de situation bloquante : le reste du programme continue de fonctionner normalement.
Une conception plus claire
Généralement, un programme répartis en plusieurs threads bénéficie d’une structure plus claire. Un développeur devant intervenir sur le code pourra en déchiffrer plus aisément la logique.
Les inconvénients du multithreading
Une synchronisation potentiellement complexe
Lorsque plusieurs threads sont à même de modifier les mêmes données, il est crucial que la coordination soit conçue de façon maîtrisée et il peut en résulter une certaine complexité du développement.
Un débogage difficile
Le threading peut être la source d’erreurs difficiles à détecter car dépendant de l’ordre exact dans lequel les threads ont été exécutés.
Risques de deadlock
Un deadlock peut survenir lorsque deux threads attendent chacun que l’autre libère une ressource particulière.
Multithreading vs multiprocessing
Lorsqu’un programme comporte plusieurs threads, deux ou plusieurs actions semblent se dérouler en parallèle. En réalité, ce n’est qu’une illusion pour ce qui concerne Python.
Le GIL
Il existe une situation propre à Python et qui s’appelle le GIL : Global Interpreter Lock, (Verrou global de l’interpréteur). Il s’agit d’un mécanisme propre à Python qui veille à ce que, à un moment donné, un seul thread soit en train de s’exécuter.
Pourquoi un tel mécanisme ? Il se trouve que le GIL a été introduit à une époque où les processeurs multi-cœur n’étaient pas très répandus. Or, tous les threads partagent la même mémoire. Le GIL a donc fait en sorte qu’une structure de donnée ne puisse être modifiée que par un seul thread à la fois. Ce mécanisme a ainsi apporté une sécurité de la gestion de la mémoire, mais il a limité la capacité de Python à exécuter des threads de façon réellement parallèle, sur plusieurs cœurs de processeurs.
Un seul thread à la fois
Dans le cadre de Python, on parle davantage de « concurrence » que de « parallélisme ». Quelle est la nuance ?
- Dans le contexte de la concurrence, à un moment donné, une ou plusieurs instructions d’un thread particulier vont être actives. En d’autres termes, chaque thread va fonctionner en solo à un moment donné, et la priorité est donnée à l’un ou l’autre selon les circonstances. Ainsi, le code de chaque thread progresse régulièrement et donne l’impression d’une exécution parallèle.
- Dans le contexte du parallélisme, plusieurs processus sont réellement exécutés simultanément. Et chacun de ces processus dispose de sa propre mémoire.
De ce fait, le multithreading est différent du multiprocessing.
Qu’en conclure ? Qu’il sera efficace d’exploiter le threading pour des tâches indépendantes impliquant de l’attente (comme un clic de l’utilisateur) et que le multiprocessing sera plus approprié dès lors que des tâches impliquant de lourds calculs peuvent être actives en parallèle.