Programmation asynchrone Python: un bref aperçu

Lorsqu'ils parlent de l'exécution d'un programme, alors «exécution asynchrone» signifie une situation où le programme n'attend pas l'achèvement d'un certain processus, mais continue de fonctionner indépendamment de celui-ci. Un exemple de programmation asynchrone est un utilitaire qui, fonctionnant de manière asynchrone, écrit dans un fichier journal. Bien qu'un tel utilitaire puisse échouer (par exemple, en raison d'un manque d'espace disque libre), dans la plupart des cas, il fonctionnera correctement et peut être utilisé dans divers programmes. Ils pourront l'appeler, lui transmettre les données pour l'enregistrement, et après cela, ils pourront continuer à faire leur propre chose.



L'utilisation de mécanismes asynchrones lors de l'écriture d'un certain programme signifie que ce programme s'exécutera plus rapidement que sans utiliser de tels mécanismes. En même temps, ce qui devrait être lancé de manière asynchrone, comme un utilitaire de journalisation, devrait être écrit en tenant compte des urgences. Par exemple, un utilitaire de journalisation, si l'espace disque est épuisé, peut simplement arrêter la journalisation et ne pas "planter" le programme principal avec une erreur.

L'exécution de code asynchrone implique généralement l'opération d'un tel code dans un thread séparé. C'est - si nous parlons d'un système avec un processeur monocœur. Sur les systèmes dotés de processeurs multicœurs, ce code peut très bien être exécuté par un processus utilisant un cœur séparé. Un processeur monocœur à un instant donné ne peut lire et exécuter qu'une seule instruction. C'est comme lire des livres. Vous ne pouvez pas lire deux livres en même temps.

Si vous lisez un livre et que quelqu'un d'autre vous en donne un autre, vous pouvez prendre ce deuxième livre et commencer à le lire. Mais le premier devra être reporté. L'exécution de code multithread est organisée sur le même principe. Et si plusieurs de vos copies lisaient plusieurs livres à la fois, cela ressemblerait au fonctionnement des systèmes multiprocesseurs.

Si sur un processeur monocœur, il est très rapide de basculer entre des tâches qui nécessitent une puissance de calcul différente (par exemple, entre certains calculs et la lecture de données à partir d'un disque), alors vous pouvez avoir l'impression qu'un cœur à processeur unique fait plusieurs choses en même temps. Ou, disons, cela se produit si vous essayez d'ouvrir plusieurs sites dans un navigateur à la fois. Si le navigateur utilise un flux séparé pour charger chacune des pages, tout se fera beaucoup plus rapidement que si ces pages se chargeaient une à la fois. Le chargement d'une page n'est pas une tâche si difficile, il n'utilise pas les ressources du système au maximum, par conséquent, le lancement simultané de plusieurs de ces tâches est un mouvement très efficace.

Programmation asynchrone Python


Au départ, Python utilisait des coroutines basées sur un générateur pour résoudre des tâches de programmation asynchrones. Puis, en Python 3.4, le module asyncio est asyncio (parfois son nom est écrit comme async IO ), qui implémente des mécanismes de programmation asynchrones. Python 3.5 a introduit la construction async / wait.

Afin de faire un développement asynchrone en Python, vous devez gérer quelques concepts. Ce sont la coroutine et la tâche.

Coroutines


En règle générale, la coroutine est une fonction asynchrone. Coroutine peut également être un objet renvoyé par une fonction coroutine.

Si, lors de la déclaration d'une fonction, il est indiqué qu'elle est asynchrone, alors vous pouvez l'appeler à l'aide du mot-clé await :

 await say_after(1, 'hello') 

Une telle construction signifie que le programme sera exécuté jusqu'à ce qu'il rencontre une expression d'attente, après quoi il appellera la fonction et suspendra son exécution jusqu'à ce que le travail de la fonction appelée soit terminé. Après cela, d'autres coroutines pourront également démarrer.

La suspension d'un programme signifie que le contrôle retourne à la boucle d'événements. Lorsque vous utilisez le module asyncio , la boucle d'événements effectue toutes les tâches asynchrones, effectue les E / S et effectue les sous-processus. Dans la plupart des cas, les tâches sont utilisées pour exécuter la corutine.

Les tâches


Les tâches vous permettent d'exécuter des coroutines dans une boucle d'événements. Cela simplifie le contrôle d'exécution de plusieurs coroutines. Voici un exemple qui utilise des coroutines et des tâches. Notez que les entités déclarées à l'aide de la construction async def sont des coroutines. Cet exemple est tiré de la documentation officielle de Python.

 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')}")    #     (      #  2 .)    await task1    await task2    print(f"finished at {time.strftime('%X')}") asyncio.run(main()) 

La fonction say_after() a le préfixe async ; par conséquent, nous avons coroutine. Si nous nous éloignons un peu de cet exemple, nous pouvons dire que cette fonction peut être appelée comme ceci:

     await say_after(1, 'hello')    await say_after(2, 'world') 

Avec cette approche, cependant, les coroutines sont invoquées séquentiellement et prennent environ 3 secondes pour se terminer. Dans notre exemple, ils sont lancés de manière compétitive. Pour chacun d'eux, une tâche est utilisée. En conséquence, le temps d'exécution de l'ensemble du programme est d'environ 2 secondes. Veuillez noter que pour qu'un tel programme fonctionne, il ne suffit pas de simplement déclarer la fonction main() avec le async . Dans de telles situations, vous devez utiliser le module asyncio .

Si vous exécutez l'exemple de code, un texte similaire au suivant s'affiche à l'écran:

 started at 20:19:39 hello world finished at 20:19:41 

Notez que les horodatages des première et dernière lignes diffèrent de 2 secondes. Si vous exécutez cet exemple avec un appel séquentiel de corutine, la différence entre les horodatages sera déjà de 3 secondes.

Exemple


Dans cet exemple, le nombre d'opérations nécessaires pour calculer la somme de dix éléments d'une séquence de nombres est déterminé. Les calculs sont effectués à partir de la fin de la séquence. Une fonction récursive commence par obtenir le nombre 10, puis s'appelle avec les nombres 9 et 8, additionnant ce qui sera retourné. Cela continue jusqu'à ce que les calculs soient terminés. Par conséquent, il s'avère, par exemple, que la somme d'une séquence de nombres de 1 à 10 est 55. En même temps, notre fonction est très inefficace, la construction time.sleep(0.1) est utilisée ici.

Voici le code de fonction:

 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) 

Que se passe-t-il si vous réécrivez ce code à l'aide de mécanismes asynchrones et appliquez asyncio.gather construction asyncio.gather , qui est chargée d'exécuter deux tâches et d'attendre qu'elles se terminent?

 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 

En fait, cet exemple fonctionne même un peu plus lentement que le précédent, car tout est exécuté dans un seul thread, et les appels à create_task , gather et d'autres comme ça créent une charge supplémentaire sur le système. Cependant, le but de cet exemple est de démontrer la capacité de participer à plusieurs tâches et d'attendre qu'elles se terminent.

Résumé


Il existe des situations dans lesquelles l'utilisation des tâches et de la corutine est très utile. Par exemple, si un programme contient un mélange d'entrées-sorties et de calculs, ou si différents calculs sont effectués dans le même programme, vous pouvez résoudre ces problèmes en exécutant du code dans un environnement compétitif plutôt que en mode séquentiel. Cela permet de réduire le temps nécessaire au programme pour effectuer certaines actions. Cependant, cela ne permet pas, par exemple, d'effectuer des calculs simultanément. Le multitraitement est utilisé pour organiser ces calculs. Il s'agit d'un grand sujet distinct.

Chers lecteurs! Comment Ă©crivez-vous du code Python asynchrone?


Source: https://habr.com/ru/post/fr475246/


All Articles