5 points par GN⁺ 2025-07-20 | 1 commentaires | Partager sur WhatsApp
  • L’asynchronisme et la concurrence sont des concepts souvent confondus, mais ils ont des significations différentes
  • L’asynchronisme désigne la possibilité que des tâches s’exécutent indépendamment de l’ordre
  • La concurrence désigne la capacité d’un système à faire progresser plusieurs tâches en même temps
  • L’absence de distinction claire entre ces deux notions dans les langages et les écosystèmes de bibliothèques engendre inefficacité et complexité
  • Dans le langage Zig, la séparation entre asynchronisme et concurrence permet la coexistence de code synchrone et asynchrone sans duplication du code

Introduction : pourquoi distinguer asynchronisme et concurrence

Grâce à la célèbre présentation de Rob Pike, la phrase « la concurrence n’est pas le parallélisme » est bien connue, mais un point plus important encore, en pratique, mérite d’être souligné : la nécessité du concept d’« asynchronisme ». D’après la définition de Wikipédia,

  • Concurrence : capacité d’un système à traiter plusieurs tâches en même temps, par partage du temps ou en parallèle
  • Calcul parallèle : exécution simultanée de plusieurs tâches au niveau physique réel
    Au-delà de cela, il existe un concept important que l’on néglige souvent : l’« asynchronisme ».

Exemple 1 : enregistrer deux fichiers

Si l’ordre n’a pas d’importance pour enregistrer deux fichiers (A, B),

io.async(saveFileA, .{io})
io.async(saveFileB, .{io})
  • enregistrer A avant B ou B avant A revient au même, et alterner entre les deux pendant l’exécution ne pose pas de problème
  • même enregistrer entièrement le fichier A avant de commencer B reste correct du point de vue du code

Exemple 2 : deux sockets (serveur, client)

Lorsqu’il faut créer un serveur TCP et connecter un client dans un même programme,

io.async(Server.accept, .{server, io})
io.async(Client.connect, .{client, io})
  • dans ce cas, les deux opérations doivent nécessairement se dérouler avec recouvrement
  • autrement dit, pendant que le serveur accepte une connexion, le client doit aussi tenter de se connecter
  • si l’on traite cela en série comme dans le premier exemple avec les fichiers, on n’obtient pas le comportement attendu

Clarification des concepts

Les notions d’asynchronisme, de concurrence et de parallélisme sont définies ainsi

  • Asynchronisme (asynchrony) : propriété selon laquelle un résultat reste correct même si les tâches s’exécutent hors de l’ordre prévu
  • Concurrence (concurrency) : capacité à faire progresser plusieurs tâches en même temps, que ce soit en parallèle ou par exécution fractionnée
  • Parallélisme (parallelism) : capacité physique à exécuter plusieurs tâches réellement au même moment

Les deux exemples, enregistrement de fichiers et connexion de sockets, sont asynchrones, mais le second (serveur-client) exige nécessairement de la concurrence

L’intérêt concret de distinguer asynchronisme et concurrence

Sans cette distinction, on rencontre les problèmes suivants

  • les auteurs de bibliothèques doivent écrire deux fois le code, en versions asynchrone et synchrone (par exemple : redis-py vs asyncio-redis)
  • pour les utilisateurs, le code asynchrone devient « contagieux » : une seule dépendance asynchrone peut obliger à rendre l’ensemble du projet asynchrone, ce qui est contraignant
  • pour éviter cela, des contournements peu élégants apparaissent, provoquant souvent des deadlocks et des inefficacités

Une séparation claire entre les deux concepts apporte donc de grands avantages, aussi bien aux auteurs de bibliothèques qu’aux utilisateurs

Zig : séparer asynchronisme et concurrence

Le langage Zig utilise io.async pour l’asynchronisme, mais cela ne garantit pas la concurrence

  • autrement dit, même avec io.async, l’exécution peut rester en interne monothread et bloquante
  • par exemple
    io.async(saveFileA, .{io})
    io.async(saveFileB, .{io})
    
    ce code peut, dans un environnement bloquant, se comporter comme
    saveFileA(io)
    saveFileB(io)
    
  • ainsi, même si l’auteur d’une bibliothèque utilise io.async, l’utilisateur conserve la possibilité d’exécuter le tout en I/O bloquantes séquentielles s’il le souhaite

Mise en place de la concurrence et mécanisme de changement de tâche (ordonnancement)

Lorsque la concurrence est nécessaire, un fonctionnement réellement efficace suppose

  1. l’utilisation d’I/O pilotées par événements plutôt que bloquantes (epoll, io_uring, etc.)
  2. l’usage de primitives de changement de tâche (switching), comme yield
  • à titre d’exemple, Zig utilise en environnement green threads une technique de stack swapping pour effectuer ces changements de tâche
  • comme dans l’ordonnancement des threads au niveau OS, l’état — registres CPU, pile, etc. — est sauvegardé et restauré afin de passer d’une tâche à l’autre
  • un tel mécanisme est indispensable pour ordonnancer de manière concurrente du code asynchrone
  • les implémentations de coroutines sans pile (par exemple suspend, resume) reposent sur le même principe

Coexistence du code synchrone et du code asynchrone

Si l’on exécute deux appels à saveData via io.async comme ci-dessous,

io.async(saveData, .{io, "a", "b"})
io.async(saveData, .{io, "c", "d"})
  • comme les deux opérations sont asynchrones l’une par rapport à l’autre, même une fonction écrite de manière synchrone peut naturellement être ordonnancée dans un contexte concurrent
  • ni l’utilisateur ni l’auteur de la bibliothèque n’ont donc besoin de dupliquer le code pour faire coexister fonctions synchrones et asynchrones

Indiquer explicitement les situations où la concurrence est « indispensable »

Certaines fonctions spécifiques (par exemple accept d’un serveur TCP) doivent exprimer explicitement dans le code qu’elles nécessitent de la concurrence à l’exécution

  • dans Zig, cela se distingue via une fonction explicite comme io.asyncConcurrent
  • cette approche permet de produire une erreur si l’environnement d’exécution ne prend pas en charge la concurrence pour cette tâche
  • contrairement à io.async, qui vise l’asynchronisme, la garantie de concurrence est ici indispensable, d’où une implémentation sous forme de fonction pouvant échouer

Conclusion

  • L’asynchronisme et la concurrence sont des concepts totalement différents et doivent être distingués clairement
  • il est possible de faire coexister code synchrone et code asynchrone
  • le modèle asynchronisme/concurrence de Zig permet d’utiliser les deux mondes sans duplication de code
  • cette structure a déjà trouvé des applications dans d’autres langages comme Go et propose une voie pour dépasser la nature contagieuse de async/await
  • avec la nouvelle conception des I/O asynchrones de Zig, on peut espérer un environnement de programmation concurrente/asynchrone encore plus intuitif à l’avenir

1 commentaires

 
GN⁺ 2025-07-20
Commentaire Hacker News
  • J’ai toujours trouvé qu’il était très difficile de définir async. Je fais moi-même partie des personnes qui ont conçu async en JavaScript, et je ne suis pas d’accord avec la définition proposée dans cet article. Le simple fait d’être async ne garantit pas un fonctionnement correct. Même dans du code async, on peut toujours rencontrer plusieurs types de race conditions au niveau utilisateur, qu’un langage prenne en charge async/await ou non. La définition à laquelle je suis arrivé récemment est qu’async, c’est « du code explicitement structuré pour la concurrence ». Cette vision mérite encore d’être affinée. J’ai aussi rédigé un billet à ce sujet : Quite a few words about async

    • Je pense qu’il est important de distinguer l’asynchronisme comme concept abstrait de ses implémentations concrètes, ces dernières incluant aussi bien des abstractions du langage que des mécanismes de coordination plus mécaniques. Au niveau d’abstraction le plus élevé, l’asynchronisme est simplement l’opposé du synchronisme. En général, quand plusieurs acteurs doivent fonctionner ensemble — par exemple lorsqu’une tâche doit se terminer pour qu’une autre puisse avancer — le cœur de l’asynchronisme est que le moment où cela se produit est inconnu ou non défini. Cette définition n’est pas difficile en soi. Le problème, c’est la charge cognitive qui apparaît quand on essaie de concevoir cette abstraction au niveau du langage.

    • Je ne suis pas un expert du sujet, mais à mon avis le code async sert essentiellement à transformer des opérations bloquantes en opérations non bloquantes afin que d’autres tâches puissent progresser en même temps. Dans mon cas en particulier, dans des boucles embarquées, du code bloquant pendant longtemps peut casser les E/S et provoquer des erreurs visibles ou audibles, donc cette manière de voir me paraît très claire.

    • Je me demande même s’il faut vraiment définir async. Si c’est si difficile à définir en pratique, c’est peut-être justement parce qu’aucun concept unique n’y correspond parfaitement. Je doute aussi qu’il soit nécessaire de définir précisément async ou l’event loop. Dans la partie physique de la puce où le parallélisme réel est possible, il doit exister une foule de concepts que je ne connais pas. Moi, il me suffit de comprendre les « user finger » (toucher du doigt, etc.), les « quickies » (des tâches extrêmement courtes), la job queue, ainsi que les API bloquantes et non bloquantes. Pour atteindre mon objectif, une API non bloquante est ce qu’il me faut, parce qu’elle permet de confier les opérations longues à des sous-systèmes, tandis que je n’écris que des « quickies » comme stocker les données voulues, avec éventuellement d’autres quickies pour les cas de succès ou d’échec. La distinction entre sync et async ne m’aide pas beaucoup. Bien sûr, il faut comprendre ces notions quand d’autres en parlent. Fondamentalement, pour moi, async correspond à une API non bloquante. Un modèle de programmation async, c’est en réalité une manière d’écrire de petites opérations bloquantes atomiques — du point de vue du temps d’exécution — en les faisant réagir à des événements « chaotiques et non déterministes ». Quoi que fasse l’intérieur du système, je pars du principe que le navigateur, l’OS ou l’appareil lui-même fournit plusieurs unités d’exécution et un bon ordonnanceur. async est, à mes yeux, un concept mal défini. Et même s’il était définissable, je ne suis pas sûr que ce soit vraiment utile. Les notions d’événement, de nature bloquante des tâches que j’écris, de fermetures de fonctions, ou encore de ce qui se découpe en jobs séparés quand j’utilise une API, me semblent bien plus pratiques. Le terme même de « callback » m’a énormément embrouillé au début. Je croyais que le code s’arrêtait là, alors qu’en réalité il faut comprendre avec précision quel code s’exécute quand le « callback » est appelé après que la section courante a fini, et quelles informations il peut voir. Honnêtement, c’est à la fois chaotique et génial. Le modèle fondamental — événements, tâches bloquantes, file de travaux, API non bloquantes — est bien plus simple que le terme async lui-même. Et il est aussi très important de comprendre ce que je fais, moi, et ce que font de leur côté le navigateur, l’OS, etc. Par exemple, en cpp, on déclare un modèle concurrent, mais c’est l’OS qui s’occupe de l’exécution réelle. En JS, via une API non bloquante, on déclare au navigateur ou à Node qu’il y a « peut-être » de la concurrence, et eux la gèrent ensuite réellement en interne. Le plus important est de garder chaque tâche courte (<50ms) et de simplement exprimer son intention via des API non bloquantes. cpp ou rust indiquent à l’OS qu’il faut exécuter les vraies tâches de manière concurrente, ce qui permet de conserver la réactivité de l’UI même s’il n’y a physiquement qu’un seul thread. Au final, le travail du programmeur async consiste à construire un « beau modèle UX » et à bien mapper les événements sur des quickies.

  • L’auteur semble avoir extrait la notion de « yield » de la définition de la concurrence pour en faire un nouveau terme, « asynchrony », puis affirme que sans cette notion la concurrence entière s’effondre. À mon avis, la concurrence implique déjà intrinsèquement la possibilité de céder l’exécution ; c’est un concept important, mais le sortir à part sous un nouveau terme ne fait qu’ajouter de la confusion.

    • Je considère que le parallélisme 1:1 est une forme de concurrence sans yield. En dehors de cela, toute autre forme de concurrence non parallèle exige de céder l’exécution à une certaine fréquence, même au niveau des instructions. Par exemple, avec CUDA, lorsque des threads d’un même warp divergent, ils finissent par entrelacer leurs instructions, ce qui permet à une branche de bloquer l’autre.

    • Il faut souligner que le texte cité indique justement explicitement que « le yield est un concept de la concurrence ».

    • La concurrence n’implique pas nécessairement le yield. Une logique synchrone a besoin d’une synchronisation explicite, et le yield n’est qu’un moyen de synchronisation parmi d’autres. Quand je parle de logique asynchrone, je désigne une concurrence qui fonctionne sans synchronisation ni yield. D’un point de vue concret, ni la concurrence ni la logique asynchrone n’existent pleinement sur une machine de von Neumann.

  • Dans ce contexte, l’asynchronisme est une abstraction qui sépare la préparation/soumission d’une requête de la collecte de son résultat. Cela permet de soumettre plusieurs requêtes puis de n’en vérifier les résultats qu’ensuite. Cela autorise une implémentation concurrente, sans pour autant l’imposer. Malgré tout, le but de cette abstraction est bien d’obtenir de la concurrence. Sans concurrence, il n’y a plus de bénéfice à en tirer. Certaines abstractions asynchrones sont même impossibles à implémenter sans un minimum de concurrence. Par exemple, une approche à base de callback peut être imitée dans un environnement mono-thread, mais avec des limites, comme des deadlocks lorsqu’on détient un mutex non récursif. Autrement dit, une abstraction asynchrone sans concurrence finira forcément par échouer. Si le demandeur lance la requête tout en tenant le mutex et que le callback s’exécute avant l’unlock, alors l’unlock peut ne jamais être exécuté. Il faut au minimum un thread séparé pour permettre au demandeur d’atteindre l’unlock.

    • D’accord. L’exemple serveur-client de l’article n’est qu’un cas parmi d’autres, et le cas que tu mentionnes exige une solution complètement différente. Je pense qu’on trouvera encore beaucoup d’autres cas similaires. En fin de compte, utiliser async impose de toujours garantir une certaine forme de concurrence.
  • « Le multitâche coopératif n’est pas préemptif. » Le terme « asynchrone » désigne souvent « mono-thread, multitâche coopératif (yield explicite) et piloté par événements », avec des opérations externes exécutées concurremment et dont les résultats sont remontés sous forme d’événements. Dans un modèle multithread ou à exécution concurrente, l’asynchronisme n’a plus vraiment beaucoup de sens : même si ce thread se bloque, le programme continue d’avancer, et les points de yield n’ont plus besoin d’être explicites.

    • Rust, C#, F#, OCaml (5+) etc. prennent en charge à la fois les threads de l’OS et async. Les threads de l’OS conviennent aux tâches CPU-bound, async aux tâches IO-bound. Le principal avantage d’async, ou d’un ordonnanceur M:N à la Go, est qu’on peut multiplier librement les tâches/goroutines tant qu’on a assez de mémoire. Avec les threads de l’OS, on se heurte au coût des context switches, au manque de threads ou de mémoire, etc. Dès qu’on dépasse un certain volume d’E/S, on finit même par rencontrer des problèmes de deadlock.
  • Les nouvelles idées de Zig autour des E/S me paraissent innovantes pour le développement d’applications généralistes. Pour ceux qui n’ont pas besoin de coroutines stackless, c’est idéal. En revanche, cela risque d’augmenter le nombre d’erreurs dans l’écriture de bibliothèques. Un auteur de bibliothèque aura du mal à savoir si les E/S fournies sont mono-thread ou multi-thread, ou si elles reposent sur des événements. Le code lié à la concurrence, à l’asynchronisme ou au parallélisme est déjà difficile à écrire même quand on connaît parfaitement toute la pile d’E/S ; c’est encore pire quand les E/S viennent de l’extérieur. Si l’interface d’E/S devient aussi vaste qu’un « petit OS », le nombre de scénarios à tester explose. Je ne suis pas sûr que les seules primitives async fournies par l’interface suffisent à couvrir tous les edge cases réels. Pour prendre en charge plusieurs implémentations d’E/S, le code devra être extrêmement « défensif » et supposer en permanence les E/S les plus parallèles possibles. Je m’attends en particulier à ce qu’il soit difficile de mélanger cette approche avec des coroutines stackless. Pour éviter de lancer des coroutines inutilement, il faudra probablement du polling explicite de coroutine, et la plupart des développeurs n’écriront sans doute pas ce genre de code eux-mêmes. Au final, on retombera probablement sur une structure assez proche du code async/await classique. En tenant compte aussi du dispatch dynamique et de la tendance de Zig à la conception bottom-up, le langage risque de devenir assez haut niveau au final. Comme il n’existe pas encore de cas d’usage réels, appeler cela une approche « sans compromis » me semble prématuré. Il faudra attendre quelques années d’usage pour pouvoir juger sérieusement.

    • Les coroutines stackless sont de toute façon prévues, puisqu’elles sont nécessaires pour la cible WASM. Le dispatch dynamique n’est utilisé que s’il existe au moins deux implémentations d’E/S ; s’il n’y en a qu’une, il est remplacé par des appels directs. Comme rien de tout cela n’a encore été validé en production, parler de « sans compromis » est en effet prématuré. J’ai entendu dire que le langage Jai utilise avec succès un modèle similaire — avec un contexte d’E/S implicite plutôt qu’un passage de contexte explicite — mais on ne peut pas vraiment dire que cela ait été éprouvé sur le terrain.

    • Je suis d’accord sur le fait que, pour prendre en charge à la fois l’exécution synchrone et asynchrone, le code doit toujours supposer les E/S les plus parallèles possibles. Mais si l’asynchronisme est correctement implémenté dans les gestionnaires d’événements E/S de bas niveau, il suffit d’appliquer le même principe partout. Au pire, le code s’exécutera simplement de manière séquentielle — donc plus lentement — sans tomber dans des problèmes de race conditions ou de deadlocks.

  • Je trouve l’idée de Zig excellente en ce qu’elle évite d’avoir à utiliser deux bibliothèques distinctes. En revanche, je m’inquiète toujours des tests de code asynchrone. Comment être sûr qu’un test passé aujourd’hui reproduit réellement tous les scénarios et tous les ordres d’exécution possibles en conditions réelles ? Les programmes à threads ont le même problème, mais le code multithread est toujours plus difficile à écrire et à déboguer. J’évite autant que possible d’utiliser des threads. Le vrai problème, c’est de faire comprendre correctement aux développeurs ce qu’est un environnement asynchrone ou multithread. J’ai récemment travaillé avec une équipe qui utilisait moitié JS, moitié Python dans un système Python, et qui avait rendu une grosse base de code async et threaded. Sauf qu’ils ne savaient même pas ce qu’était le Global Interpreter Lock (GIL). J’avais l’impression de ne faire que les sermonner. Et en plus, leurs tests continuaient à passer même quand on cassait réellement le code. mangum force la finalisation des tâches de fond et des tâches async à la fin d’une requête HTTP, et ils n’en avaient aucune idée. Et même quand on explique ce genre de choses, tout le monde s’en fiche. Le problème n’est pas seulement de savoir ; c’est surtout que d’autres acceptent d’y prêter attention.

    • Zig prévoit d’introduire une implémentation de test pour Io, afin de rendre possibles les stress tests, y compris le fuzzing, sous des modèles d’exécution parallèles. Mais l’essentiel, c’est que la plupart des bibliothèques n’auront probablement jamais besoin d’appeler directement io.async ou io.asyncConcurrent. Par exemple, la majorité des bibliothèques de base de données peuvent se contenter d’un code purement synchrone. Le développeur de l’application peut ensuite rendre ce code asynchrone très simplement avec quelque chose comme io.async(writeToDb) et io.async(doOtherThing). C’est moins sujet aux erreurs et bien plus facile à comprendre que d’avoir à saupoudrer du async/await partout dans le code.

    • Je suis d’accord : tester tous les entrelacements possibles dans du code asynchrone ou multithread est notoirement difficile. Même avec des fuzzers ou des frameworks de test de concurrence, il est difficile d’avoir confiance sans l’expérience acquise en production. Dans les systèmes distribués, c’est encore pire. Par exemple, quand on conçoit une infrastructure de webhooks, il faut gérer non seulement l’async dans son propre code, mais aussi toutes sortes de problèmes externes comme les retries réseau, les timeouts ou les échecs partiels. Sous forte concurrence, des questions comme les retries, la déduplication et les garanties d’idempotence deviennent elles-mêmes des sujets d’ingénierie à part entière. C’est pour cela qu’il devient utile d’utiliser des services spécialisés comme Vartiq.com (j’y travaille). Ces services permettent d’abstraire une partie de la complexité opérationnelle de la concurrence et de réduire la blast radius, mais les problèmes de test async dans mon propre code restent entiers. En résumé, async, le threading et la concurrence distribuée amplifient mutuellement leurs risques ; la communication et la conception du système comptent donc davantage que n’importe quelle syntaxe ou bibliothèque.

  • Je pense que l’auteur mélange les définitions de la concurrence dans ce billet ; il vaut la peine de lire l’article de Lamport.

    • Ne te contente pas de laisser un lien vers le papier, explique un peu. Pour ma part, je trouvais la définition plutôt correcte. Par exemple : asynchronisme = si des tâches restent correctes même sans s’exécuter dans l’ordre, alors c’est asynchrone ; concurrence = propriété d’un système capable de faire avancer plusieurs tâches en même temps, que ce soit par parallélisme réel ou par changement de tâche ; parallélisme = exécution simultanée réelle de deux tâches ou plus au niveau physique.

    • C’est pour cette raison que j’ai complètement cessé d’utiliser ces termes. Peu importe avec qui j’en parle, chacun en a une compréhension différente, donc ces mots n’ont plus vraiment de valeur pour communiquer.

    • L’auteur savait aussi, dans son billet, qu’il existait déjà des définitions établies de ce terme. Il a simplement proposé sa propre définition ; tant qu’elle reste cohérente en interne, cela suffit. La seule question est de savoir si les lecteurs l’accepteront.

    • La moitié de l’article de Lamport ne peut même pas être exprimée conceptuellement dans la plupart des langages. Quand on crée des threads, on ne se met presque jamais à raisonner en termes d’ordre total et d’ordre partiel. Ce type de discussion devient utile quand on conçoit des protocoles avec TLA+. Il n’y a pas besoin d’élever au rang de nouvelle théorie le fait qu’une fonction de l’API async de Zig ne fonctionne « que dans un environnement d’exécution asynchrone » et provoque sinon une erreur de compilation.

  • Une bonne manière d’évaluer si le terme « asynchronisme » est vraiment nécessaire consiste à se demander s’il reste utile dans des modèles de concurrence variés, pas seulement dans un seul langage ou un seul modèle. Par exemple, s’il est utile de manière transversale en Haskell, Erlang, OCaml, Scheme, Rust, Go, etc., alors il a une réelle valeur. En général, dès qu’on introduit un ordonnancement coopératif, il faut faire bien plus attention aux blocages du système entier, aux problèmes de latence, etc., à cause d’un seul morceau de code. Avec un ordonnancement préemptif, une grande partie de ces problèmes disparaît, parce qu’un verrouillage total du système devient impossible, ce qui réduit fortement la classe des problèmes à gérer.

  • « Asynchrony » est ici un terme mal choisi ; il existe déjà un terme mathématique bien défini : la « commutativité ». Certaines opérations ne dépendent pas de l’ordre (addition, multiplication, etc.), tandis que d’autres oui (soustraction, division, etc.). En général, l’ordre des opérations dans le code est représenté par le numéro des lignes, de haut en bas ; dans du code async, cet ordre se brise. Écrit de cette manière, asyncConcurrent(...) ne peut qu’être assez déroutant. À moins d’avoir complètement intégré le billet de blog, il est difficile de comprendre ce que cela signifie. Zig — et Rust aussi, que j’aime pourtant — tombe souvent dans ce genre d’approche un peu « hipster » : soit on implémente, comme les lifetimes de Rust, une logique procédurale d’ordre/commutativité basée sur async, soit on utilise simplement ce que les gens connaissent déjà.

    • Je ne suis pas d’accord avec l’idée que « asyncConcurrent(...) est déroutant ». Une fois l’idée centrale du billet intégrée, ce n’est plus du tout confus. Qu’il vaille la peine d’apprendre cette idée est une autre question. En pratique, des gens vont assimiler ce concept, l’expérimenter, et avec le temps on verra si cela fonctionne bien sur le terrain ou non. Et remplacer cela par le mot « commutativité » serait encore plus déroutant dans Zig, puisque le langage possède déjà des opérateurs commutatifs. Avec f() + g(), comme l’addition est commutative, on pourrait facilement se demander si Zig a donc le droit d’exécuter cela en parallèle. L’ordre d’exécution et la commutativité sont des choses totalement différentes, il faut donc les distinguer.

    • À strictement parler, la commutativité est une propriété d’une opération (binaire). Si on dit que deux instructions async comme connect et accept commutent, la question devient : « par rapport à quelle opération ? » Pour l’instant, l’opérateur bind (>>=), ou éventuellement .then(...), est ce qui s’en rapproche le plus, mais on reste encore dans le domaine de l’intuition.

    • L’asynchronisme autorise aussi un ordre partiel. Même si deux opérations doivent être retirées dans le même ordre, leur ordre d’exécution réel peut être différent. Par exemple, la soustraction n’est pas commutative, mais on peut tout de même calculer le solde et le montant débité via deux requêtes exécutées en parallèle, puis appliquer le résultat dans l’ordre approprié.

    • Le fait qu’un autre terme puisse englober cette idée ne signifie pas qu’il soit meilleur qu’« asynchrony ». « Commutativity » est un mot lourd à lire, à entendre et à écrire. asynchrony est beaucoup plus familier.

    • L’argument de la commutativité a ses limites. Si A et B commutent chacun avec C, alors ABC = CAB, mais cela ne signifie pas nécessairement que ACB soit identique aussi. Dans le cas asynchrone, il faut que ABC = ACB = CAB. Il existe peut-être déjà un terme mathématique pour cela, mais je ne le connais pas.

  • En tant que programmeur réseau, j’ai énormément écrit de code concurrent, parallèle et asynchrone, et ce billet me paraît un peu confus. J’ai l’impression qu’il cherche des réponses au-dessus d’une abstraction déjà bancale. Si les outils ou l’implémentation eux-mêmes sont défectueux, le problème est justement qu’ils peuvent se « casser » aussi facilement. En réalité, déboguer du code multithread est même assez amusant. Quand je vois à quel point les autres redoutent les monstres multithread, ça me procure presque du plaisir.