Guide de la programmation asynchrone dans Python

Guide de la programmation asynchrone dans Python

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!

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 similaires

Découvrez Tous les articles
S’amuser tout en gossant avec la technologie est une...
En lire plus
C'est bien beau de vouloir apprendre un langage de...
En lire plus
Carrière passionnante, la programmation de jeux vidéos est de...
En lire plus
Complémentarité, objectivité, expertise, efficacité et économie... Voilà quelques raisons...
En lire plus

Emplois en vedette

Chargé(e) de comptes chez Groupe Velan Média
  • Date de publication30 septembre 2020
  • EntrepriseEspresso Jobs
  • VilleMontréal
Développeur(-euse) front end - React
Développeur(-euss) web senior/ Lead developer
  • Date de publication28 septembre 2020
  • EntrepriseAtypic
  • VilleMontreal
Développeur(-euse) back end
Agent(e) aux ventes et service-client chez Groupe Velan Média
  • Date de publication24 septembre 2020
  • EntrepriseEspresso Jobs
  • VilleMontréal
Développeur(-euse) back end
#
Recevez une alerte ciblée!

Soyez le premier informé des postes offerts correspondant à votre profil.

Inscrivez-vous
#

Soyez informé de nos dernières offres d’emploi, nouvelles et articles.

Range

KM