Vayel / Python-3.5

Un article pour ZdS sur la version 3.5 de Python
0 stars 1 forks source link

Rôle de async et await #12

Closed Vayel closed 9 years ago

Vayel commented 9 years ago

Dans l'article et dans la PEP, il est dit que les mots-clés async et await ont été ajoutés pour faciliter la programmation asynchrone en Python. Mais les coroutines, ce n'est pas uniquement de l'asynchrone, si ?

De plus, il est dit dans la PEP que cela permet de dsitinguer une coroutine d'un générateur. Mais je ne comprends pas trop la différence. Est-ce le simple fait de pouvoir passer des données à la coroutine (ce qui se faisait avec send) et non au générateur ?

cgabard commented 9 years ago

Alors en théorie une coroutine est un concept général de fonction qui peut être suspendu et reprise un peu partout. Donc non ce n'est pas que pour l'asynchrone. En pratique, en python avec cette pep, ce n'est utilisé que pour ça et pour faciliter la programation asynchrone. D'ailleurs le choix des mots clés le montre bien que c'est fait pour ça.

Pour les générateurs, il me semblait l'avoir noté, mais ici il n'y a pas vraiment de différence. En théorie un générateur est une sous-forme de co-routine. En pratique les générateurs en Python, avec send, faisaient déjà plus que ce qu'est le concept de generateur. Et donc les deux sont très semblable dans python 3.5. Mais il est possible qu'à l'avenir les différences se creusent.

En gros les générateurs de python faisaient déjà plus que ce qu'un générateur était censé faire du coup ils ont introduit les coroutine pour acceuillir ces utilisations avancés et les futures modifications.

Vayel commented 9 years ago

Il faudrait que je lise la PEP en entier, mais une fonction async sans await a-t-elle un sens ? Le cas échéant, il me semble important d'expliquer les mots-clés séparément.

J'ai à peu près compris la différence entre un générateur et une coroutine (normale, pas dans Python3.4): on peut envoyer des données à la seconde, pas au premier. Mais il faudrait que je relise des trucs là-dessus.

Mais du coup, que fait-on dans l'article ? Explique-t-on les choses à des gens maîtrisant les concepts de générateur et de coroutine (on fournirait notamment les explications que tu viens de me donner) ou aux autres (on vulgarise énormément, en disant que async et await servent à faire de l'asynchrone sans les callbacks) ?

cgabard commented 9 years ago

Il faudrait que je lise la PEP en entier, mais une fonction async sans await a-t-elle un sens ? Le cas échéant, il me semble important d'expliquer les mots-clés séparément.

En soit oui si tu as besoin de passer une autre fonction a besoin d'une co-routine comme parametre alors que tu n'as aucune opération avec des io à faire dedans.

J'ai à peu près compris la différence entre un générateur et une coroutine (normale, pas dans Python3.4): on peut envoyer des données à la seconde, pas au premier. Mais il faudrait que je relise des trucs là-dessus.

Voila. Tu as tout compris. D'où le fait que les générateurs de python en faisaient déjà trop.

Mais du coup, que fait-on dans l'article ? Explique-t-on les choses à des gens maîtrisant les concepts de générateur et de coroutine (on fournirait notamment les explications que tu viens de me donner) ou aux autres (on vulgarise énormément, en disant que async et await servent à faire de l'asynchrone sans les callbacks) ?

Je ne sais pas. Je pense qu'il faut revoire la formulation pour mieux expliquer.

Je ne vois pas d'interet d'expliquer les coroutine sans liaison avec l'asynchrone puisque c'est fait pour ça et ne sera utilisé que pour ça.

Vayel commented 9 years ago

En soit oui si tu as besoin de passer une autre fonction a besoin d'une co-routine comme parametre alors que tu n'as aucune opération avec des io à faire dedans.

Pour moi, jusqu'à maintenant, une coroutine en prog asynchrone c'était une fonction avec un yield from dedans. Ce dernier est remplacé par await. Mais async n'avait pas d'équivalent, je crois, je ne comprends donc pas son rôle.

cgabard commented 9 years ago

Si il fallait mettre le décorateur asyncio.coroutine justement pour les cas où il n'y a pas de yield from dedans.

Christophe Le 24 août 2015 19:56, "Vincent" notifications@github.com a écrit :

En soit oui si tu as besoin de passer une autre fonction a besoin d'une co-routine comme parametre alors que tu n'as aucune opération avec des io à faire dedans.

Pour moi, jusqu'à maintenant, une coroutine en prog asynchrone c'était une fonction avec un yield from dedans. Ce dernier est remplacé par await. Mais async n'avait pas d'équivalent, je crois, je ne comprends donc pas son rôle.

— Reply to this email directly or view it on GitHub https://github.com/Vayel/Python-3.5/issues/12#issuecomment-134317060.

Vayel commented 9 years ago

En relisant la section concernée, je me suis rendu compte que je ne comprenais pas à quoi servaient async with et async for. Dans tous les cas, que la fonction soit async ou non, l'appel est asynchrone, non ? async sert juste à dire qu'il y aura des await dedans, non ?

Du coup, si l'appel de la fonction est asynchrone, je ne comprends pas l'intérêt d'avoir de l'asynchrone dedans (ça revient, il me semble, à ouvrir un thread pour une boucle for alors qu'on est déjà dans un thread).

cgabard commented 9 years ago

Il faut comprendre qu'il y a une différence entre :

with await toto():

et

async with toto():

Le premier va se suppendre le temps d'excuter toto() puis reprendre l'execution et faire le with. Mais que se passe t'il quand le début et la fin du with (les méthodes __enter__ et __exit__ ) devraient être des coroutines car elles fond des appels io ?

en fait le premier code serait équivalent à :


titi = await toto()
titi.__enter__()

try:
    # contenu du bloc
finally:
    titi.__exit__()

alors que le deuxième :


titi = toto()
await titi.__aenter__()

try:
    # contenu du bloc
finally:
    await titi.__aexit__()

De même pour le for : le async for permet que chaque appel à l'itérateur, pour récupérer la valeur suivant, soit fait de manière asynchrone.

Du coup, si l'appel de la fonction est asynchrone, je ne comprends pas l'intérêt d'avoir de l'asynchrone dedans (ça revient, il me semble, à ouvrir un thread pour une boucle for alors qu'on est déjà dans un thread).

Je ne suis pas sûrs de comprendre ton interogation mais non pas de thread là dedans.

Toute les boucles événementielles (syncio, twisted, tornado, node.js) fonctionnent toute dans un seul thread. Dans un modèle impératif, quand il y a une opération qui va impliquer des entrées/sorties (requêtes web, appel à une base de données, etc.), le processeur se met à glander en attendant d'avoir sa réponse. Or, point de vue processeur, une requete web c'est trèèèèèèèèès long. L'idée est donc de faire autre chose pendant ce temps. D'où les await : on dit à la boucle évenementielle de asyncio "Là j'appel une coroutine qui risque de prendre du temps à me répondre, si tu as autre chose à faire, c'est le moment". La boucle va alors en profiter pour reprendre une autre co-routine qui avait été suspendu en attendant et reprendra celle-ci que plus tard, quand les données seront arrivées.

Donc tout se passe toujours dans le même thread. Simplement on spécifie explicitement dans le code où il peut être suspendu.

Si tu reprend l'exemple dans le TL;DR tu verra que les await sont toujours sur des opérations impliquant des entrées/sorties. Car c'est à ce moment là que ça vaut le coup de suspendre la fonction pour faire autre chose car le processeur va glander. Evidement ça n'a vraiment de l'intéret que si tu as potentiellement plusieurs coroutines qui s'executent en parralèle, ce qui est généralement le cas sur le web : tu as au moins une coroutine pour traiter chaque requete de chaque client. Ainsi le serveur peut faire le rendu du template du client A pendant que le client B attend des données de la base de données. Ça permet d'en faire plus sur un seul thread.

Christophe.

2015-08-26 18:20 GMT+02:00 Vincent notifications@github.com:

En relisant la section concernée, je me suis rendu compte que je ne comprenais pas à quoi servaient async with et async for. Dans tous les cas, que la fonction soit async ou non, l'appel est asynchrone, non ? async sert juste à dire qu'il y aura des await dedans, non ?

Du coup, si l'appel de la fonction est asynchrone, je ne comprends pas l'intérêt d'avoir de l'asynchrone dedans (ça revient, il me semble, à ouvrir un thread pour une boucle for alors qu'on est déjà dans un thread).

— Reply to this email directly or view it on GitHub https://github.com/Vayel/Python-3.5/issues/12#issuecomment-135085729.

cgabard commented 9 years ago

Mais bon il faut clairement que je repasse sur cette section pour la clarifier. Regarde plutot le reste le temps que j'ai le temps de refaire l'explication

Vayel commented 9 years ago

Merci pour tes explications très claires.

cgabard commented 9 years ago

Ça a le mérite de prouver qu'il faut que je clarifie cette section

Christophe.

On Wed, Aug 26, 2015 at 9:44 PM, Vincent notifications@github.com wrote:

Merci pour tes explications très claires.

— Reply to this email directly or view it on GitHub https://github.com/Vayel/Python-3.5/issues/12#issuecomment-135150322.

Vayel commented 9 years ago

J'y ai réfléchi cette nuit et en fait je pensais qu'il se passait cela :

Mais en fait non. Du coup, si j'ai bien compris, un appel asynchrone n'a de sens que lorsqu'on a des io, i.e. un appel vers un truc extérieur à Python. Mais si jamais ce qui bloque est Python lui-même (par exemple, inverser une grosse matrice), ça n'a pas de sens, si ?

cgabard commented 9 years ago

Ce n'est pas que ça n'a pas de sens, c'est que du coup c'est inutile surtout. Tout le principe de la programmation asynchrone au sens asyncio/tornado/twisted/node.js c'est d'occuper le processeur pendant qu'il glandouillerai a attendre un truc lent comme des io. Il n'y a pas de raisons a priori à l'utiliser quand ce que tu fais est du travail CPU.

Cependant on peut imaginer, si le calcul est vraiment long, l'utiliser tout de même. L'idée serait de lancer ce calcul long dans un process séparé (pour qu'il tourne sans la contrainte du GIL sur un autre coeur de ton processeur). Du point de vue de ton thread principal ça revient au même : une longue opération hors du thread qui nous bloque le temps qu'elle soit terminé. Ça a bien sûrs été prévu. Tu peux transformer une fonction normal en coroutine executé dans un pool de thread ou process séparé avec ce genre de fonctions. Lexecutor est ce qui va executer ton code, soit des thread, soit des process.

Les thread sont pratique si c'est une opération io bloquante. Tu peux ainsi la transformer en version non-bloquante a moindre coup car le GIL est toujours libéré pendant les io. Avec a un executor à thread tu peux transformer requests ou les opérations de bases sur les fichiers facilement.

Si ton opération demande du CPU, il faut passer par un process pour que ça serve à quelque chose (le calcul se passe sur un autre coeur, ton thread principal peut faire autre chose). Mais evidement créer un process coute cher (fork au sens OS) et les communications entre les deux prennent aussi du temps (pas de mémoire partagé). Du coup ça n'a d'intéret que si le calcul est un peu volumineux. Inverser une matrice 2x2 n'a aucun interet.

Donc pour résumer, oui ces approches là n'ont principalement de l'interet que pour les applications avec beaucoup d'io mais tu peux te débrouiller pour contourner le GIL pour les gros calculs CPU. Mais ça reste marginal. Il n'y a aucune raison d'utiliser ça si ton appli ne fait majoritairement que du calcul. Ce type de programmation asynchrone n'a d'interet que si la majorité du traitement c'est de l'attente d'io.

Christophe.

2015-08-27 10:21 GMT+02:00 Vincent notifications@github.com:

J'y ai réfléchi cette nuit et en fait je pensais qu'il se passait cela :

  • Appel asynchrone à une fonction f
  • Fait autre chose
  • Notification que l'appel à f est terminé

Mais en fait non. Du coup, si j'ai bien compris, un appel asynchrone n'a de sens que lorsqu'on a des io, i.e. un appel vers un truc extérieur à Python. Mais si jamais ce qui bloque est Python lui-même (par exemple, inverser une grosse matrice), ça n'a pas de sens, si ?

— Reply to this email directly or view it on GitHub https://github.com/Vayel/Python-3.5/issues/12#issuecomment-135337817.