Conçu pour les développeurs à la croisée de la curiosité et du développement professionnel, ce guide vise à illustrer comment la programmation asynchrone peut améliorer les performances, l'évolutivité et la réactivité des applications. En nous concentrant sur les principes universels des opérations non bloquantes et des boucles d'événements, nous allons au-delà de technologies spécifiques comme AsyncIO, Node.js ou Go.
Cette exploration est conçue pour les développeurs de logiciels désireux de saisir l’efficacité des logiciels modernes sans la complexité du jargon technique.
Imaginez qu'un thread unique gérant les requêtes soit comparable à une route à voie unique dans une ville animée : chaque requête correspond à une voiture, et lorsqu'il n'y a qu'une seule voie, les voitures s'alignent, provoquant des retards. Mais que se passerait-il si notre ville – notre service – pouvait gérer le trafic plus intelligemment ? C'est là que la magie d'un environnement d'exécution asynchrone entre en jeu. C'est comme ajouter un système de circulation intelligent à notre ville, guidant les voitures sur plusieurs voies et intersections sans attente.
Ce système assure une circulation fluide, garantissant qu'aucune voiture (ou tâche) n'attende trop longtemps, ce qui est précisément la manière dont un environnement d'exécution asynchrone permet à notre logiciel de fonctionner efficacement.
Prenons l'exemple d'un service effectuant des opérations d'E/S de manière synchrone. Pour plus de clarté, ces opérations sont présentées en dehors du flux d'exécution principal dans le diagramme :
Le blocage des E/S au démarrage de votre service peut être acceptable, mais il est conseillé d'éviter cette approche lors du traitement des requêtes externes. Le schéma suivant illustre comment l'efficacité du service peut être améliorée grâce à l'adoption d'opérations d'E/S non bloquantes :
Ces exemples illustrent des scénarios où les gains de performances du serveur sont maximisés. Néanmoins, l'avantage des opérations non bloquantes persiste quelle que soit la fréquence des requêtes entrantes. Même dans des conditions moins qu'idéales, l'intégration de threads d'E/S dédiés au workflow de traitement améliore les performances.
Le temps total nécessaire au traitement d'une requête (de la requête initiale du client à la réponse finale, comme indiqué par le nombre bleu à droite) diminuera invariablement, à condition que le nombre de threads soit suffisant pour gérer toutes les requêtes. Dans le pire des cas, cette durée ne dépassera pas celle des méthodes de traitement synchrones.
Nous rencontrons alors une pratique qui pourrait sembler contre-intuitive à de nombreux développeurs. Lorsque les opérations d'E/S représentent une part importante du temps de traitement des requêtes, l'optimisation d'autres segments de code peut apporter une légère amélioration. La durée de récupération des données d'un cache peut correspondre étroitement au temps consacré à la logique métier et au rendu des modèles.
L’utilisation de la mise en cache ou d’une base de données en cours de traitement peut réduire les temps de récupération des données par rapport à d’autres activités de traitement.
Pour segmenter efficacement le code et faciliter l'exécution des rappels, il est possible d'ordonner au runtime de passer au cycle de boucle d'événements suivant. Voici un exemple illustratif de l'application de ce concept :
// blocking callbacks function func1_cb(str, cb) { var res = func1(str); cb(res); } function func2_cb(str, cb) { var res = func2(str); cb(res); } // non-blocking callbacks function func1_cb(str, cb) { var res = func1(str); process.nextTick(function () { cb(res); }); } function func2_cb(str, cb) { var res = func2(str); process.nextTick(function () { cb(res); }); } // usage example func1_cb(content, function (str) { func2_cb(str, function (result) { // work with result }); });
En adoptant cette méthodologie pour diviser les calculs en deux parties, le temps de traitement global reste identique pour les scénarios avec des demandes quasi simultanées. Cependant, la réponse à la première demande est retardée :
Ce scénario représente le résultat le moins favorable avec la stratégie du « tick suivant ». Comme le montre l'exemple initial, cette méthode est efficace pour les requêtes peu fréquentes. Si le rythme des requêtes est modéré, cette technique accélère le traitement en permettant l'intercalation de nouvelles requêtes et le démarrage d'opérations non bloquantes pendant les pauses d'exécution. Cette approche réduit efficacement le temps de traitement total et le temps moyen par requête :
En conclusion, l'adoption d'E/S non bloquantes est essentielle pour améliorer les performances des applications et s'avère avantageuse dans les environnements où les volumes de requêtes entrantes sont faibles et importants. De plus, un séquençage efficace du flux d'exécution, illustré par des concepts similaires à la technique du « next tick », améliore considérablement l'efficacité du serveur. L'adoption de ces pratiques de programmation asynchrone offre des avantages évidents par rapport aux méthodes synchrones traditionnelles.