Découvrons la programmation asynchrone en Python

Magie noire et concurrence

a marqué ce sujet comme résolu.

Tout le monde se secoue ! :D

J'ai commencé (il y a 7 heures) la rédaction d'un article au doux nom de « Découvrons la programmation asynchrone en Python » et j'ai dans l'objectif de proposer en validation un texte aux petits oignons. Je fais donc appel à votre bonté sans limite pour dénicher le moindre pépin, que ce soit à propos du fond ou de la forme. Vous pourrez consulter la bêta à votre guise à l'adresse suivante :

Merci !

+7 -0

Très beau travail. J'essayerai de faire une relecture précise. Dans un premier temps, j'aurais juste ajouté une flèche verticale vers le bas sur les schémas pour indiquer où est le temps (au début, je lisais de gauche à droite et ne comprenais pas pourquoi on revenait en arrière).

+0 -0

C'est à croire que la cuisine est un lieu inspirant pour l'asynchrone https://zestedesavoir.com/contenus/884/lasynchrone-et-le-multithread-en-net/#1-await-async-un-couple-inseparable

Par contre tes image sont juste magiques ! Bravo à toi.

artragis

Oui j'estimais qu'un diagramme valait mieux qu'un long discours alors j'ai sorti Dia de sous sa couche de poussière pour rendre le phénomène plus visible.

@Vayel : C'est vraiment gênant ? Je pensais que ce style de représentation (tiré d'UML) était assez répandu…

+0 -0

@Vayel : C'est vraiment gênant ? Je pensais que ce style de représentation (tiré d'UML) était assez répandu…

nohar

Vraiment non, mais ça ne coûte pas grand chose de l'ajouter sur le premier diagramme uniquement. :)

+0 -0

Merci pour la prise en compte de ma remarque. :)

Mes remarques plus précises ci-dessous. Vu la qualité du contenu, je me permets de faire dès maintenant des remarques sur la forme.

Introduction

cette bibliothèque standard dans la bibliothèque standard

Une bibliothèque stantard, c'est pas justement une bibliothèque dans la bibliothèque standard ?

avaient dessiné les contours avant elle : la programmation asynchrone.

Tu pourrais mettre en valeur "programmation asynchrone".

Mais enfin, à quoi ça sert ? Et comment ça marche ?

C'est un détail, mais peut-être qu'un bloc Question serait le bienvenu ici.

Ça veut dire quoi, asynchrone ?

C'est parce qu'un vrai serveur fonctionne de façon asynchrone :

Encore un détail, mais j'ai un peu buté sur cette formulation. A priori, elle fait référence à "Il aurait l'air un peu idiot, non ?", qui n'est pas la dernière phrase.

Concurrence et parallélisme

Mais pas en Python, ni dans aucun autre langage de la même famille que lui

Par curiosité, quelle est cette famille ?

Où les interruptions sont prédictibles et explicites, donc tout le code entre deux interruptions est atomique,

Par "atomique", tu veux dire qui s'exécute d'un seul coup ? Il me semble intéressant d'expliciter ce terme.

Une boucle événementielle, c'est essentiel

En effet, tout l'intérêt de la programmation asynchrone est d'être capable d'occuper le programme pendant qu'une tâche donnée est en attente d'un événement.

Je ne crois pas que tu l'aies dit plus haut, donc il me semble important ici de faire le rapport avec yield. Jusqu'à présent, tu as juste indiqué que ce mot-clé permettait de suspendre une coroutine, mais tu n'as pas lié ça à la notion d'IO.

Dotons tout de même notre classe Loop d'une dernière méthode pour exécuter la boucle jusqu'à épuisement d'une coroutine en particulier :

Si j'ai bien compris, c'est équivalent à faire un schedule() suivi d'un run_until_empty() ?

Le code suivant a-t-il un intérêt particulier ?

1
2
3
>>> event_loop = Loop()
>>> event_loop.schedule(tic_tac())
>>> event_loop.run_until_complete(spam())

C'est comme ça que j'avais interprété ta phrase avant de lire le code, et non pas comme un raccourci pour schedule + run_until_empty.


Je m'arrête ici pour l'instant. C'est toujours aussi agréable à lire.

Par contre, est-ce volontaire de ne plus parler des IO ? A partir du moment où on commence à coder, on manipule uniquement des tâches qu'on peut stopper, sans faire le parallèle avec les IO bloquantes que tu mentionnes dans les sections précédentes.

Encore merci.

+1 -0

Introduction

cette bibliothèque standard dans la bibliothèque standard

Une bibliothèque stantard, c'est pas justement une bibliothèque dans la bibliothèque standard ?

C'est plus un effet de style qu'autre chose. Asyncio redéfinit beaucoup de choses qui sont déjà dans la bibliothèque standard pour les rendre asynchrones, comme asyncio.subprocess, asyncio.sleep. Ça la rend gloutonnique et c'est probablement la première chose qui choque quand on arrive sur sa doc. D'où la formulation "bibliothèque standard dans la bibliothèque standard".

avaient dessiné les contours avant elle : la programmation asynchrone.

Tu pourrais mettre en valeur "programmation asynchrone".

OK.

Mais enfin, à quoi ça sert ? Et comment ça marche ?

C'est un détail, mais peut-être qu'un bloc Question serait le bienvenu ici.

J'aime pas les blocs question quand la question est rhétorique ou à moitié rhétorique. C'est pour cette raison que je ne l'ai pas mise ici dan un bloc question. J'ai déjà mis un bloc information plus bas dans l'intro, et celui-ci casse bien le rythme (c'est l'effet escompté). Bref, je pense qu'il vaut mieux réserver ce bloc (à utiliser avec parcimonie) pour l'insérer dans le corps du texte, parce que sa première vocation est de réveiller le lecteur après un pavé.

Ça veut dire quoi, asynchrone ?

C'est parce qu'un vrai serveur fonctionne de façon asynchrone :

Encore un détail, mais j'ai un peu buté sur cette formulation. A priori, elle fait référence à "Il aurait l'air un peu idiot, non ?", qui n'est pas la dernière phrase.

OK je vais reformuler.

Concurrence et parallélisme

Mais pas en Python, ni dans aucun autre langage de la même famille que lui

Par curiosité, quelle est cette famille ?

Les langages "de script" (même si je déteste cette appellation réductrice). Qui sont haut niveau, exécutés par une machine virtuelle, typés dynamiquement, qui affranchissent le développeur de la gestion directe de la mémoire. J'hésitais à rajouter Perl dans l'énumération…

Où les interruptions sont prédictibles et explicites, donc tout le code entre deux interruptions est atomique,

Par "atomique", tu veux dire qui s'exécute d'un seul coup ? Il me semble intéressant d'expliciter ce terme.

OK je le ferai, j'y pensais en me relisant hier. Cela signifie effectivement que le code est exécuté d'un bloc, avec la garantie que l'opération ne sera pas interrompue.

Une boucle événementielle, c'est essentiel

En effet, tout l'intérêt de la programmation asynchrone est d'être capable d'occuper le programme pendant qu'une tâche donnée est en attente d'un événement.

Je ne crois pas que tu l'aies dit plus haut, donc il me semble important ici de faire le rapport avec yield. Jusqu'à présent, tu as juste indiqué que ce mot-clé permettait de suspendre une coroutine, mais tu n'as pas lié ça à la notion d'IO.

C'est pas faux. Je me suis concentré sur la notion de concurrence sur cette partie, mais je m'aperçois que nulle part je ne dis que "dans la vraie vie, on ne fait jamais de yield explicite avec asyncio". Dans la précédente version de cet article j'avais une secion où on montrait que yield pouvait être utilisé exactement comme un trap pour faire une sorte d'appel système, ce qui rendait évident le fait que les IO passaient par là, mais je l'ai sucrée parce qu'elle rajoutait de la complexité à l'article pour pas grand chose. Il va donc falloir que je colle une ou deux phrases à ce sujet.

Dotons tout de même notre classe Loop d'une dernière méthode pour exécuter la boucle jusqu'à épuisement d'une coroutine en particulier :

Si j'ai bien compris, c'est équivalent à faire un schedule() suivi d'un run_until_empty() ?

Le code suivant a-t-il un intérêt particulier ?

1
2
3
>>> event_loop = Loop()
>>> event_loop.schedule(tic_tac())
>>> event_loop.run_until_complete(spam())

Oui, il lance les tâches tic_tac() et spam() en concurrence, et s'arrête quand spam() a fini de travailler, même si tic_tac() n'a pas fini.

Par contre, est-ce volontaire de ne plus parler des IO ? A partir du moment où on commence à coder, on manipule uniquement des tâches qu'on peut stopper, sans faire le parallèle avec les IO bloquantes que tu mentionnes dans les sections précédentes.

J'ai répondu plus haut. Je vais corriger ça. Le truc c'est que quand on commence à coder, on essaye déjà d'avoir un modèle de concurrence, sans s'occuper des IO, mais il faut vraiment que j'explique le rapport entre faire une IO et suspendre une tâche. J'espère juste trouver un moyen de le faire convenablement sans avoir à parler de select.

+0 -0

Bonjour les agrumes !

La bêta a été mise à jour et décante sa pulpe à l'adresse suivante :

Merci d'avance pour vos commentaires.

CHANGELOG:

  • Prise en compte des remarques de Vayel,
  • Ajout d'un paragraphe à la fin de la troisième section pour :
    • faire le lien avec les IO.
    • faire le lien avec la section suivante.
+0 -0

Bon vu que l'article a déjà eu un an pour mûrir avant d'arriver en bêta ici, je l'envoie dès ce soir en validation. Au pire si des remarques sont faites d'ici là je les intégrerai et mettrai à jour la version en validation.

+0 -0

Je te garantis une relecture sur la suite demain matin. Si tu souhaites néanmoins envoyer en validation ce soir, j'aurais une petite remarque sur la section "Appels de coroutines" :

Supposons maintenant qu'une coroutine ait besoin de faire appel à une autre coroutine, pour lui déléguer du travail.

Peut-être pourrais-tu faire le parallèle avec la fin de la section précédente :

Cela veut dire que dans un code asynchrone "de la vraie vie" on n'écrit jamais explicitement yield ; on appelle plutôt des coroutines "natives" qui le font pour nous.

+0 -0

Ben c'est un peu le cas, ça s'enchaîne naturellement, mais je peux rajouter un petit liant pour que ça soit plus naturel.

Dans tous les cas, l'article est en validation mais ça n'empêche pas de le mettre à jour d'ici qu'un valido ait le temps de le relire. Donc si tu fais une relecture demain ça passera tout seul.

+0 -0

Du coup j'ai l'occasion de relire la suite ce soir.

Appels de coroutines

RAS

La syntaxe asynchrone de Python 3.5

Dans celui-ci, nous allons nous contenter simuler

Petite typo.

Dans celui-ci, nous allons nous contenter simuler des entrées-sorties en appelant la coroutine asyncio.sleep.

Ligne 9 du code, tu as un saut de ligne en trop à priori.

Le problème du fast-food

Dans cet exemple, on place un verrou sur le bac à frites pour qu'un seul serveur puisse y accéder à la fois.

Si on considère que plusieurs serveurs peuvent accéder au bac en même temps, pour vérifier "si les frites sont déjà en cours de préparation, il est inutile de lancer une nouvelle fournée !", aurait-on pu avoir une variable globale PREPARING, utilisée de la manière suivante ?

1
2
while PREPARING:
    yield

Conclusion

programmes bien réels tirant partie

Je crois qu'il y a une typo.

Bonne soirée !

+0 -0

Ligne 9 du code, tu as un saut de ligne en trop à priori.

Hmm, en fait si on suivait rigoureusement la PEP-08, ce n'est pas ce saut de ligne qui serait en trop, mais plutôt, il manquerait un saut de ligne après chaque fonction. Ici je vais me contenter de virer le saut de ligne. Un seul c'est largement suffisant vu que les fonctions font à peine 3 lignes chacune.

Si on considère que plusieurs serveurs peuvent accéder au bac en même temps, pour vérifier "si les frites sont déjà en cours de préparation, il est inutile de lancer une nouvelle fournée !", aurait-on pu avoir une variable globale PREPARING, utilisée de la manière suivante ?

Non, Python t'enguirlanderait parce que tu aurais mis un yield dans une coroutine définie avec async. Ici, l'attente asynchrone se fait avec le async with FRIES_LOCK. Si les frites sont en préparation, alors à la fin de la préparation on finit par acquérir le verrou pour le relâcher immédiatement après avoir décrémenté le compteur.

Noté pour les typos.

+0 -0
Ce sujet est verrouillé.