Espace publicitaire
Guide de la programmation asynchrone dans Python
Jean-Michel Clermont-Goulet
13 mai 2019
TI, Web
5 minutes à lire
1 417
La programmation asynchrone dans Python, comment ça fonctionne? Espresso-Jobs s’est penché sur la question.
Lorsque nous parlons d’exécution de programme, le terme « asynchrone » signifie que ledit programme n’attend pas qu’un processus particulier se termine et continue quand même.
Un exemple d’une programmation asynchrone est l’écriture d’un programme dans un fichier journal : bien qu’il soit possible que ça foire (disons parce que le journal a rempli l’espace du disque dur), très souvent, il ne le fait pas. Vous pouvez donc écrire votre programme pour appeler les routines du journal de manière asynchrone.
Pour faire court, une exécution asynchrone signifie que le programme principal roule un peu plus vite. Votre code de journalisation, le logging code, doit être écrit de telle sorte que s’il remplit l’espace de votre disque, il arrête simplement la journalisation plutôt que de planter.
Généralement, effectuer un code asynchrone implique un threading (un filetage), s’il s’agit d’un seul noyau, avec le multi-core, il peut être géré par un autre processus s’exécutant sur un noyau différent. Un seul noyau ne peut lire qu’un seul ensemble d’instructions à la fois et exécuter.
C’est comme un livre ; vous ne pouvez qu’en lire un à la fois!
Si vous lisez un bouquin et qu’une personne vous en passe un autre, et que vous passez à ce deuxième livre? C’est propre à l’exécution filetée. Et, pour donner un exemple aussi concret, un groupe lit des livres côte à côte, il s’agira de « multitraitement ».
Voici ce que David Bolton de dice.com a concocté comme dossier pour vulgariser le tout. Prenez des notes!
Avant asyncio (parfois écrit async IO), une conception de programmation simultanée en Python, il existait des coroutines basées sur des générateurs. Et bien Python 3,10 les supprime. Le module asyncio a été ajouté dans Python 3,4, suivi par asyncio/await dans le 3.5.
Voici quelques concepts asynchrones que vous devriez comprendre : les coroutines et les tâches. Regardons d’abord les coroutines.
Une coroutine est habituellement une fonction avec une définition asynchrone. Il peut également s’agir d’un objet retourné par une « fonction coroutine ». Il est recommandé d’utiliser la syntaxe async/await pour développer des programmes asyncio.
En marquant une fonction comme étant async, elle peut être appelée avec l’instruction awaitstatement comme await say_after(1,'hello';). Le terme await signifie que le programme s’exécutera jusqu’à l’instruction en attente, l’awaitstatement, appellera la fonction et suspendra son exécution jusqu’à ce que la fonction soit terminée ; les autres coroutines ont maintenant une chance de s’exécuter.
Cette suspension d’exécution signifie que le contrôle est renvoyé à la boucle d’événements (event loops). Lorsque vous utilisez asyncio, une boucle d’événement exécute toutes les tâches asynchrones, exécute le réseau d’entrée-sortie (network IO) ainsi que des sous-processus. La plupart du temps, lorsque vous écrivez des coroutines, vous utiliserez des tâches pour les exécuter.
Généralement, lorsque vous écrivez des coroutines, vous utiliserez des tâches.
Mieux connues dans le monde de l’informatique comme tasks, les tâches vous permettent d’exécuter une coroutine dans une boucle d’événements, simplifiant ainsi la gestion de l’exécution de plusieurs coroutines.
Voici un exemple, tiré de la documentation officielle de Python; notez que tout ce qui est défini avec async def est une coroutine.
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
task1 = asyncio.create_task(
say_after(1, 'hello'))
task2 = asyncio.create_task(
say_after(2, 'world'))
print(f"started at {time.strftime('%X')}")
# Wait until both tasks are completed (should take
# around 2 seconds.)
await task1
await task2
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
.
La fonction say_after() a async comme préfixe et est donc une coroutine. En mettant de côté l’exemple ci-dessus pendant une seconde, vous pourriez appeler la fonction say_after() directement comme ceci :
await say_after(1, 'hello')
await say_after(2, 'world')
.
Mais cela exécute séquentiellement les coroutines et prend trois secondes. L’exemple plus large ci-dessus les exécute simultanément, en utilisant une tâche pour chacun, et ils prennent deux secondes. Notez que définir une fonction main() n’est pas suffisant. Avec main étant asynchrone, il doit être géré par asyncio.
Lorsque vous exécutez le grand exemple, vous obtenez cette sortie ; notez la différence de deux secondes entre les temps. Si vous exécutez le plus petit exemple, la différence sera de trois secondes.
started at 10:36:11
hello
world
finished at 10:36:13
.
David Belton de dice.com a mis en pratique quelques exemples intéressants. L’opération qu’il effectue détermine combien d’opérations sont complétées lors du calcul de la somme des dix premiers éléments, bien qu’il le fasse… à l’envers !
import time
def fib(n):
global count
count=count+1
time.sleep(0.1)
if n > 1:
return fib(n-1) + fib(n-2)
return n
start=time.time()
global count
count = 0
result =fib(10)
print(result,count)
print(time.time()-start)
.
Ensuite, je refais le même calcul en utilisant asyncio concurrency, et il utilise une fonction asyncio. gather(), qui exécute ici deux tâches et attend que les deux aient terminé.
import asyncio,time
async def fib(n):
global count
count=count+1
time.sleep(0.1)
event_loop = asyncio.get_event_loop()
if n > 1:
task1 = asyncio.create_task(fib(n-1))
task2 = asyncio.create_task(fib(n-2))
await asyncio.gather(task1,task2)
return task1.result()+task2.result()
return n
.
Tout ça rend un peu plus de temps, car tout tourne dans un seul thread, un seul filetage, et les create_tasks, les gather, etc. ajoute une légère surcharge de travail. Par contre, le tout vous démontre comment vous pouvez commencer simultanément plusieurs tâches à la fois et attendre qu’elles soient toutes terminées.
Les tâches et les coroutines ont leur utilité ; s’il y a un mélange d’entrée-sortie et de calcul ou différents calculs, alors vous pouvez les exécuter ensemble et réduire le temps de traitement en faisant fonctionner les choses simultanément plutôt que séquentiellement.
Lorsque nous parlons d’exécution de programme, le terme « asynchrone » signifie que ledit programme n’attend pas qu’un processus particulier se termine et continue quand même.
Un exemple d’une programmation asynchrone est l’écriture d’un programme dans un fichier journal : bien qu’il soit possible que ça foire (disons parce que le journal a rempli l’espace du disque dur), très souvent, il ne le fait pas. Vous pouvez donc écrire votre programme pour appeler les routines du journal de manière asynchrone.
Pour faire court, une exécution asynchrone signifie que le programme principal roule un peu plus vite. Votre code de journalisation, le logging code, doit être écrit de telle sorte que s’il remplit l’espace de votre disque, il arrête simplement la journalisation plutôt que de planter.
Généralement, effectuer un code asynchrone implique un threading (un filetage), s’il s’agit d’un seul noyau, avec le multi-core, il peut être géré par un autre processus s’exécutant sur un noyau différent. Un seul noyau ne peut lire qu’un seul ensemble d’instructions à la fois et exécuter.
C’est comme un livre ; vous ne pouvez qu’en lire un à la fois!
Si vous lisez un bouquin et qu’une personne vous en passe un autre, et que vous passez à ce deuxième livre? C’est propre à l’exécution filetée. Et, pour donner un exemple aussi concret, un groupe lit des livres côte à côte, il s’agira de « multitraitement ».
Voici ce que David Bolton de dice.com a concocté comme dossier pour vulgariser le tout. Prenez des notes!
Python asynchrone
Avant asyncio (parfois écrit async IO), une conception de programmation simultanée en Python, il existait des coroutines basées sur des générateurs. Et bien Python 3,10 les supprime. Le module asyncio a été ajouté dans Python 3,4, suivi par asyncio/await dans le 3.5.
Voici quelques concepts asynchrones que vous devriez comprendre : les coroutines et les tâches. Regardons d’abord les coroutines.
Une quoi? Une coroutine?
Une coroutine est habituellement une fonction avec une définition asynchrone. Il peut également s’agir d’un objet retourné par une « fonction coroutine ». Il est recommandé d’utiliser la syntaxe async/await pour développer des programmes asyncio.
En marquant une fonction comme étant async, elle peut être appelée avec l’instruction awaitstatement comme await say_after(1,'hello';). Le terme await signifie que le programme s’exécutera jusqu’à l’instruction en attente, l’awaitstatement, appellera la fonction et suspendra son exécution jusqu’à ce que la fonction soit terminée ; les autres coroutines ont maintenant une chance de s’exécuter.
Cette suspension d’exécution signifie que le contrôle est renvoyé à la boucle d’événements (event loops). Lorsque vous utilisez asyncio, une boucle d’événement exécute toutes les tâches asynchrones, exécute le réseau d’entrée-sortie (network IO) ainsi que des sous-processus. La plupart du temps, lorsque vous écrivez des coroutines, vous utiliserez des tâches pour les exécuter.
Généralement, lorsque vous écrivez des coroutines, vous utiliserez des tâches.
Les tâches
Mieux connues dans le monde de l’informatique comme tasks, les tâches vous permettent d’exécuter une coroutine dans une boucle d’événements, simplifiant ainsi la gestion de l’exécution de plusieurs coroutines.
Voici un exemple, tiré de la documentation officielle de Python; notez que tout ce qui est défini avec async def est une coroutine.
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
task1 = asyncio.create_task(
say_after(1, 'hello'))
task2 = asyncio.create_task(
say_after(2, 'world'))
print(f"started at {time.strftime('%X')}")
# Wait until both tasks are completed (should take
# around 2 seconds.)
await task1
await task2
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
.
La fonction say_after() a async comme préfixe et est donc une coroutine. En mettant de côté l’exemple ci-dessus pendant une seconde, vous pourriez appeler la fonction say_after() directement comme ceci :
await say_after(1, 'hello')
await say_after(2, 'world')
.
Mais cela exécute séquentiellement les coroutines et prend trois secondes. L’exemple plus large ci-dessus les exécute simultanément, en utilisant une tâche pour chacun, et ils prennent deux secondes. Notez que définir une fonction main() n’est pas suffisant. Avec main étant asynchrone, il doit être géré par asyncio.
Lorsque vous exécutez le grand exemple, vous obtenez cette sortie ; notez la différence de deux secondes entre les temps. Si vous exécutez le plus petit exemple, la différence sera de trois secondes.
started at 10:36:11
hello
world
finished at 10:36:13
.
En voulez-vous des exemples ? En v’là !
David Belton de dice.com a mis en pratique quelques exemples intéressants. L’opération qu’il effectue détermine combien d’opérations sont complétées lors du calcul de la somme des dix premiers éléments, bien qu’il le fasse… à l’envers !
import time
def fib(n):
global count
count=count+1
time.sleep(0.1)
if n > 1:
return fib(n-1) + fib(n-2)
return n
start=time.time()
global count
count = 0
result =fib(10)
print(result,count)
print(time.time()-start)
.
Ensuite, je refais le même calcul en utilisant asyncio concurrency, et il utilise une fonction asyncio. gather(), qui exécute ici deux tâches et attend que les deux aient terminé.
import asyncio,time
async def fib(n):
global count
count=count+1
time.sleep(0.1)
event_loop = asyncio.get_event_loop()
if n > 1:
task1 = asyncio.create_task(fib(n-1))
task2 = asyncio.create_task(fib(n-2))
await asyncio.gather(task1,task2)
return task1.result()+task2.result()
return n
.
Tout ça rend un peu plus de temps, car tout tourne dans un seul thread, un seul filetage, et les create_tasks, les gather, etc. ajoute une légère surcharge de travail. Par contre, le tout vous démontre comment vous pouvez commencer simultanément plusieurs tâches à la fois et attendre qu’elles soient toutes terminées.
Pour [enfin !] finir
Les tâches et les coroutines ont leur utilité ; s’il y a un mélange d’entrée-sortie et de calcul ou différents calculs, alors vous pouvez les exécuter ensemble et réduire le temps de traitement en faisant fonctionner les choses simultanément plutôt que séquentiellement.
Articles susceptibles de vous intéresser
Emplois susceptibles de vous intéresser
Montréal
Permanent à temps plein
Publié il y a 6 jours
Longueuil
Permanent à temps plein
Publié il y a 27 jours
Mes sauvegardes
Vous devez être connecté pour ajouter un article aux favoris
Connexion ou Créez un compte
Emploi favori
Vous devez être connecté pour pouvoir ajouter un emploi aux favories
Connexion
ou Créez un compte