Site original : Sam & Max: Python, Django, Git et du cul
En Python, les threads et l’asyncio s’utilisent ensemble
Je vois beaucoup dans les tutos ici et là des gens qui opposent asyncio avec l’ancienne manière de faire de l’IO non bloquante : les threads.
C’est une erreur : les deux méthodes ne sont pas opposées, elles sont complémentaires.
En effet, asyncio ne peut être non bloquant que sur le réseau : ça ne gère pas l’IO sur les fichiers. Par ailleurs, toute opération CPU bloque également la boucle d’évènement.
C’est très bien de ne pas bloquer sur le réseau, mais encore faut-il pouvoir faire la requête réseau.
Si votre programme est bloqué à attendre une compression zip qui dure 3 secondes, pendant ce temps, les opérations réseaux déjà lancées tournent bien.
MAIS IL N’EN LANCERA PAS DE NOUVELLES.
Pendant 3 secondes, aucune nouvelle requête ne sera faite puisque le programme ne fait qu’une chose : ziper. Ca marche pour d’autres choses hein : copie de fichier, gros calcul matheux, traverser une grande liste, etc.
Donc si vous avez fini toutes vos requêtes à la seconde 1, pendant 2 secondes, votre programme n’est pas utilisé à son plein potentiel.
Les threads ne permettent pas d’avancer plus vite, mais ils permettent de faire 2 travaux en parallèle. Par exemple, de lancer pendant ces 2 secondes des requêtes supplémentaires sur le réseau.
On a vendu asyncio comme plus léger que les threads. Ce n’est pas tout à fait exact. asyncio a les avantages suivants :
Mais si on a quelques threads, le changement de contexte entre les threads est moins important que ce que coûte asyncio à faire tourner.
Pour certains travaux où le coût d’une opération réseau est faible, mais que cumulativement toutes les opérations ralentissent votre programme, un thread sera plus performant.
Par exemple, beaucoup d’opérations sur les bases de données tombent dans cette catégorie, à moins d’avoir des très longues requêtes.
Dans ce cas, avoir un thread dédié aux opérations de la base de données peut être une bonne décision.
Si vous avez des opérations sur des fichiers ou de grosses opérations CPU, les faire travailler dans un thread peut booster votre programme. Ca tombe bien il y a une lib pour ça.
Si vous avez beaucoup de petites opérations réseau, les grouper, dans un thread à part, peut booster votre programme.
Et asyncio pour le reste, par exemple des requêtes HTTPs, DNS, SMTP, FTP, SSH, etc.
On peut donc copieusement utiliser les deux en parallèle. La bonne nouvelle, c’est que Guido a designé asyncio pour ça:
import asyncio import aiohttp async def main(loop): # ça c’est fait avec asyncio await aiohttp.get('http://bidule.com') # ça c’est fait dans un thread await loop.run_in_executor(None, gros_calcul, params) loop = asyncio.get_event_loop() loop.run_until_complete(main(loop)) |
Notez bien :
ProcessPoolExecutor
ou un ThreadPoolExecutor
pour choisir la stratégie de parallélisme à adapter : process, thread, nombres de workers, etc.Bien entendu, chaque fois que vous ajoutez un mécanisme de concurrence, vous ajoutez de la complexité, donc ne le faites que si c’est nécessaire.
Commencez avec un programme synchrone simple. Si c’est trop lent, ajouter les threads ou l’asyncio, si ça ne suffit pas, utilisez les deux.