4 points par GN⁺ 2024-03-26 | 1 commentaires | Partager sur WhatsApp
  • Dans la communauté Rust, on voit souvent revenir la question suivante : les threads peuvent faire tout ce que async/await peut faire, et plus simplement, alors pourquoi choisir async/await ?
  • Rust est un langage de bas niveau qui ne masque pas la complexité des coroutines. C’est l’opposé de langages comme Go, qui deviennent asynchrones par défaut sans que le programmeur ait besoin de penser à l’asynchronisme.
  • Les bons programmeurs essaient d’éviter la complexité, alors pourquoi async/await est-il nécessaire ?

Comprendre le contexte

  • Rust est un langage de bas niveau. Le code y est généralement linéaire : une tâche s’exécute une fois la précédente terminée.
  • Lorsqu’il faut exécuter de nombreuses tâches en même temps, comme dans un serveur web, le code linéaire pose problème.
  • Le web des débuts a tenté de résoudre ce problème en introduisant le threading.
  • On peut traiter plusieurs clients simultanément avec des threads, mais les programmeurs ont voulu faire passer la concurrence de l’espace OS à l’espace utilisateur.

Le problème des timeouts

  • L’un des plus grands atouts de Rust est sa composabilité.
  • async/await permet d’appliquer cette composabilité aux fonctions liées aux I/O.
  • Par exemple, lorsqu’on veut ajouter un timeout à une fonction de traitement client, on peut l’implémenter avec deux combinateurs.

Threads thématiques

  • Dans un exemple utilisant des threads, implémenter un timeout n’est pas simple.
  • TcpStream propose bien les fonctions set_read_timeout et set_write_timeout, mais leur usage reste limité.
  • Une méthode est proposée pour programmer un timeout à l’aide des combinateurs de Rust, mais elle reste limitée à TcpStream et nécessite des appels système supplémentaires.

Cas de réussite d’async

  • L’écosystème HTTP a adopté async/await comme principal mécanisme d’exécution.
  • tower illustre bien la puissance de async/await, avec des fonctions comme les timeouts, le rate limiting et l’équilibrage de charge.
  • macroquad est un moteur de jeu Rust qui exécute son moteur avec async/await.

Améliorer l’image d’async

  • Les avantages de async ne sont pas largement connus, ce qui peut conduire certaines personnes à le mal comprendre.
  • La communauté Rust a tendance à surestimer les gains de performance de l’async Rust et à minimiser ses avantages réellement significatifs.
  • Il faut voir async/await comme un modèle de programmation puissant, capable d’exprimer de façon concise des schémas impossibles à représenter en Rust synchrone sans des dizaines de threads et de canaux.

L’avis de GN⁺

  • async/await augmente la complexité du code lorsqu’on gère la concurrence, mais apporte en contrepartie la capacité de traiter efficacement un grand nombre de clients simultanément.
  • Cet article souligne que async/await ne se limite pas à des avantages de performance, mais possède aussi de vraies forces comme modèle de programmation.
  • Le async/await de Rust offre de la composabilité pour divers travaux d’I/O, ce qui est particulièrement utile dans des domaines comme les services réseau ou les serveurs web.
  • D’un point de vue critique, la complexité de async/await peut constituer une barrière à l’entrée pour les développeurs débutants, ce qui nécessite un effort pédagogique pour la surmonter.
  • Parmi les autres projets offrant des fonctionnalités similaires, on peut citer l’implémentation de async/await de Node.js ou la bibliothèque asyncio de Python, qui proposent elles aussi un paradigme comparable.
  • Lorsqu’on adopte async/await, il faut prendre en compte la complexité du code et sa maintenabilité ; mais lorsqu’il faut gérer un grand nombre de clients en parallèle, ce modèle apporte de grands avantages.

1 commentaires

 
GN⁺ 2024-03-26
Avis Hacker News
  • Async/await et mono-thread

    • Comme dans le modèle JavaScript, async/await en mono-thread est simple et bien compris.
    • Avec des threads, plusieurs CPU peuvent traiter le problème, et Rust aide à gérer les verrous.
    • On peut avoir des threads de priorités différentes, ce qui est nécessaire lorsque le calcul est contraint.
    • Le async/await multi-thread est complexe. Dans les sections où le calcul est contraint, le modèle peut s’effondrer.
    • En Rust, le calcul multi-thread ne fonctionne pas très bien. Parmi les problèmes :
      • Effondrement sous contention des futex : cela peut poser problème avec certains allocateurs de stockage.
      • Famine due à des mutex non équitables : le Mutex standard et les canaux crossbeam-channel ne sont pas équitables.
  • Async/await contre threads

    • La critique ne porte pas sur la complexité, mais sur le fait que, selon le choix fait, l’écosystème se fragmente et que l’une des options devient inférieure.
    • L’écosystème Rust a décidé que, pour faire de l’I/O, il fallait tout faire en async/await.
    • Si Rust avait rendu les alternatives à async/await plus composables comme abstractions, ce mécontentement aurait disparu.
  • Problèmes avec l’article

    • Il ne présente qu’un seul exemple de serveur web, et il résout mal la question des threads.
    • Les programmeurs veulent des threads conceptuels et sémantiques, pas des threads OS.
    • Les threads OS coûtent cher, et nous voulons des threads peu coûteux.
    • Il y a des problèmes dans l’implémentation des timeouts dans l’exemple du serveur web.
  • Aspects non abordés

    • async/await s’exécute sur un seul thread, donc il n’y a pas besoin de verrous ni de synchronisation.
    • La propagation des erreurs avec async/await n’est pas claire.
    • Il faudrait aussi parler de backpressure dans l’I/O réseau.
  • Point important sur l’annulation

    • Il est facile d’annuler n’importe quelle tâche future.
    • L’annulation avec des threads est complexe, et l’arrêt forcé d’un thread n’est pas fiable.
    • Dans le modèle async de Rust, on peut ajouter un timeout externe à toutes les futures.
  • Une campagne façon marketing autour de async/await

    • async/await a été une erreur technique et a eu un coût important pour la communauté.
    • Rust reste malgré tout le meilleur langage, mais on craint que ce débat ne dure éternellement.
  • Async/await contre fibres

    • Rust avait auparavant des green threads, qui ont été retirés délibérément.
    • La capacité à abandonner des futures à tout moment entraîne un coût important.
    • Il est étrange de vanter la composabilité de async/await.
  • Principaux avantages de async/await en Rust

    • Cela peut fonctionner même dans des environnements sans threads ni mémoire dynamique.
    • Cela permet d’écrire du code de façon concise en utilisant la concurrence.
  • Malentendus sur async/await

    • Certaines personnes ne comprennent pas pourquoi un mécanisme de concurrence sur un seul thread est nécessaire.
    • async/await est utile pour la programmation d’interface utilisateur, la communication avec le GPU et la communication entre runtimes.
  • Pourquoi choisir async/await plutôt que des threads

    • async/await peut réduire l’usage mémoire de l’état des clients, des requêtes et des tâches.
    • La compression de l’état est importante pour les performances dans un monde moderne où la mémoire est lente.
    • async/await et le CPS sont efficaces pour réduire l’usage mémoire par client.