web-dev-qa-db-fra.com

Asyncio vs. Gevent

Contexte

Une fois, j'ai travaillé sur un système Python2 qui avait beaucoup de code d'E/S personnalisé écrit de manière synchrone, et a été mis à l'échelle à l'aide de threads. À un moment donné, nous ne pouvions plus le faire évoluer et nous avons réalisé que nous devions passer à la programmation asynchrone.

  • Twisted était le choix populaire, mais nous voulions éviter son enfer de rappel.
  • Il avait le @inlineCallbacks décorateur, qui a efficacement implémenté des coroutines en utilisant la magie du générateur, comme l'ont fait d'autres bibliothèques. C'était plus tolérable, mais c'était un peu floconneux.
  • Et puis nous avons trouvé gevent . Il ne vous restait plus qu'à:
from gevent import monkey
monkey.patch_all()

Et juste comme ça, toutes vos E/S standard - sockets, transactions de base de données, tout ce qui est écrit en pur Python, en fait - était asynchrone, cédant et basculant dans les coulisses à l'aide de greenlets.

Ce n'était pas parfait:

  • À l'époque, cela ne fonctionnait pas bien sous Windows (et il a encore des limites aujourd'hui). Heureusement, nous fonctionnions sous Linux.
  • Il ne pouvait pas utiliser d'extensions C de singe-patch, donc nous ne pouvions pas utiliser MySQLdb, par exemple. Heureusement, il existait de nombreuses alternatives pures Python, comme PyMySQL.

Question

De nos jours, Python 3 est beaucoup plus populaire, et avec lui - asyncio . Personnellement, je pense que c'est génial, mais on m'a récemment demandé en quoi cela différait-il de ce que nous avons mis en œuvre avec gevent, et nous n'avons pas pu trouver une réponse suffisamment bonne.

Cela peut sembler subjectif, mais je recherche en fait de vrais cas d'utilisation où l'un surperformerait considérablement l'autre, ou permettrait quelque chose que l'autre ne permet pas . Voici les considérations que j'ai rassemblées jusqu'à présent:

  1. Comme je l'ai dit, gevent est plutôt limité sur Windows. Là encore, la plupart des codes de production que je connais fonctionnent sous Linux.

    Si vous devez exécuter Windows, utilisez asyncio .

  2. Gevent ne peut pas faire d'extensions C de singe-patch. Mais, asyncio ne peut pas patch monkey n'importe quoi.

    Imaginez qu'une nouvelle technologie DB apparaisse et que vous aimeriez l'utiliser, mais il n'y a pas de bibliothèque Python pure pour elle, donc vous ne pouvez pas l'intégrer à Gevent. La En fait, vous êtes tout aussi coincé quand il n'y a pas de bibliothèque io * que vous pouvez intégrer avec asyncio! Il y a des threads de travail et des exécuteurs, bien sûr, mais ce n'est pas le but, et cela fonctionne aussi bien dans les deux cas de toute façon .

  3. Certaines personnes disent que c'est une question de goût personnel, mais je pense qu'il est juste de dire que la programmation synchrone est intrinsèquement plus facile que la programmation asynchrone (pensez-y: avez-vous déjà rencontré un programmeur novice qui peut travailler avec des sockets, mais a du mal à comprendre comment les sélectionner/interroger correctement, ou penser aux futurs/promesses? Et avez-vous déjà rencontré l'inverse?).

    De toute façon, n'allons pas là-bas. Je voulais aborder ce point car il revient fréquemment ( voici une discussion sur reddit), mais ce que je recherche vraiment, ce sont des scénarios où vous avez une raison pratique d'utiliser l'un ou l'autre.

  4. Asyncio fait partie de la bibliothèque standard. C'est énorme: cela signifie qu'il est bien entretenu, bien documenté, et tout le monde le connaît et l'utilise par défaut.

    Mais, étant donné le peu de Gevent que vous devez savoir pour l'utiliser (et qu'il est assez bien entretenu et documenté également), cela ne semble pas aussi crucial. Ainsi, bien qu'il existe plusieurs réponses sur StackOverflow, même pour les scénarios les plus complexes impliquant des contrats à terme, la possibilité de ne pas utiliser de contrats à terme semble tout aussi viable.

Assurément, Guido et la communauté Python avaient une bonne raison de consacrer autant d'efforts à Guido, et même d'introduire de nouveaux mots clés dans les langues - je n'arrive pas à les trouver.

Quelles sont les principales différences entre les deux et dans quels scénarios cela devient-il apparent?

25
Dan Gittik

Réponse "simple" à partir d'une utilisation réelle:

  1. Une bonne chose à propos de gevent - vous pouvez patch choses, ce qui signifie que vous pouvez [théoriquement] utiliser des bibliothèques synchrones. C'est à dire. vous pouvez patcher Django.
  2. Mauvaise chose à propos de gevent - tout ne peut pas être corrigé, si vous devez utiliser un pilote DB qui ne peut pas être corrigé, vous êtes condamné
  3. Le pire de gevent - c'est "magique". L'effort requis pour comprendre ce qui se passe avec "patch_all" est énorme, le même effort s'applique à la recherche/à l'embauche de nouvelles personnes pour votre équipe de développement. Ce qui est encore pire - le débogage du code basé sur gevent est un enfer. Je dirais, à peu près le même enfer, que les rappels, sinon pire.

Je pense que le point ultérieur est essentiel. La chose la plus sous-estimée en génie logiciel est que le code est censé être lire, pas écrit ou exécuté efficacement (si c'est le cas plus tard, vous préféreriez passer de python vers le langage au niveau du système.) Asyncio est venu avec une partie manquante pour la programmation asynchrone - prédéfinis et contrôlés les points de changement de contexte. , files d'attente, etc.) et en utilisant await ... lorsque vous savez l'appel est IO bloquant, donc vous laissez la boucle d'événement choisir autre chose, qui est prête pour le CPU, et reprendre l'état actuel plus tard.

C'est ce qui rend asyncio si bon - c'est facile à entretenir. L'inconvénient est que presque tous les "mondes" doivent également être asynchrones - pilotes DB, outils http, gestionnaires de fichiers. Et parfois, il vous manquera des bibliothèques, c'est à peu près garanti.

10
Slam