Sam & Max » tornado http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 Quelle est la différence entre “bloquer” et “en cours d’exécution” ? 9 http://sametmax.com/quelle-est-la-difference-entre-bloquer-et-en-cours-dexecution/ http://sametmax.com/quelle-est-la-difference-entre-bloquer-et-en-cours-dexecution/#comments Tue, 09 Dec 2014 16:57:06 +0000 http://sametmax.com/?p=12766 On vous dit qu’il faut faire attention en utilisant des technologies non bloquantes, car si on bloque dans la boucle d’événement, on bloque tout le programme, et on perd l’intérêt de l’outil.

C’est vrai, mais que veut dire “bloquer” ?

Car si je fais :

for x in range(1000000):
    print(x)

Mon programme va tourner longtemps, et la boucle d’événement va bloquer, n’est-ce pas ?

En fait, “bloquer” est un abus de langage car il y a plusieurs raisons pour bloquer. Dans notre contexte, il faudrait dire “bloquer en attente d’une entrée ou d’une sortie”. D’où l’appellation “Aynschronous non blocking I/O” des technos types NodeJS, Twisted, Tornado, Gevent, etc.

En effet, il faut distinguer deux causes d’attente à votre programme :

  • Attendre que vos instructions se terminent. C’est être “en cours d’exécution”.
  • Attendre qu’un événement extérieur (écrire sur le disque, lire une socket, un clic de souris) arrive à sa conclusion. C’est bloquer sur de l’I/O.

Le premier cas est impossible à éviter. Tout au mieux pouvons-nous répartir la charge du programme sur plusieurs cœurs, processeurs voire machines. Le code devra toujours attendre qu’il se termine, mais ça ira plus vite.

Dans le contexte de la programmation non bloquante telle qu’on vous en a parlé, on est donc dans le deuxième cas.

Il ne s’agit alors pas de s’interdire de faire des boucles ou autre opération longue (ou plutôt, c’est un problème d’optimisation ordinaire qui n’a rien à voir avec le fait de bloquer), il s’agit de ne pas “attendre à ne rien faire” quand une opération extérieure est en cours.

C’est ce que font naturellement NodeJS, Twisted, Tornado, Gevent & Co. Quand on fait un échange HTTP, le bout de données part, puis le reste du code continue de tourner, passant à la tâche suivante, en attendant que le paquet traverse le réseau, atteigne l’autre machine, qui vous répond finalement. C’est ce temps, incompressible, sans contrôle de votre côté, durant lequel il ne faut pas bloquer. Le gain de perf est que votre programme ne se la touche pas pendant les temps d’attente, mais bien entendu que VOTRE, lui, code va prendre du temps et “bloquer” le processeur. Il faut bien qu’il s’exécute.

Ce qu’on entend donc par “il ne faut pas faire d’opération bloquante dans un code qui est déjà non bloquant” c’est “il ne faut pas utiliser un outil à l’API bloquante au milieu d’autres outils non bloquants”.

Par exemple, n’utilisez pas requests avec Twisted, car requests est codé pour attendre sans rien faire jusqu’à obtenir une réponse à chaque requête, bloquant Twisted. Utilisez plutôt treq. C’est pareil pour la lecture d’un fichier, une requête de base de données, etc. Et il existe des boucles d’événements ailleurs que sur le serveur : une page Web possède sa propre boucle (c’est pour cela que tout JS est asynchrone), un toolkit GUI comme QT ou GTK aussi (c’est pour ça qu’ils utilisent la programmation événementielle), etc.

Maintenant vous allez me dire : mais pourquoi bloquer alors ? Pourquoi ne pas toujours éviter de bloquer ?

Et bien parce que si on ne bloque pas, on ne peut pas écrire un programme ligne à ligne. On est obligé d’adopter un style de programmation asynchrone puisqu’on ne sait pas quand le résultat de certaines lignes va arriver. Ça veut dire des callbacks, ou des futures, ou des coroutines, ou du message passing… Bref, un truc plus compliqué. Or, on n’a pas forcément besoin de ce niveau de performance. En fait, la grande majorité des programmes n’ont pas besoin de ce niveau de performance. Donc, on bloque en attendant, non pas Godot, mais l’I/O, parce que c’est plus simple à écrire. Pour pas se faire chier.

Il y a bien des moyens de contourner ce problème : les threads, le multiprocessing, les coroutines, etc. Parfois même, on ignore le problème : bloquer quelques ms au milieu d’une boucle d’événements une fois par seconde n’est pas un drame. Une fois que j’ai fini le dossier sur les tests unitaires, je vous ferai un dossier sur la programmation non bloquante, avec aussi une esquisse de la parallélisation.

En attendant, ne stressez pas parce que votre code “bloque” parce qu’il travaille longtemps, assurez-vous juste que les APIs que vous utilisez ne bloquent pas pendant l’I/O, et vous êtes ok.

Et comment savoir ? Et bien si une donnée rentre ou sort de votre programme (ça ne fait pas partie du code source), c’est de l’I/O. Si votre code ressemble à ça :

res = faire_operation_sur_IO()
faire_un_truc_avec_le_res(res)

Alors votre outil est bloquant, puisque qu’il compte sur le fait que la deuxième ligne sera exécutée à coup sûr quand la première sera terminée. Un outil non bloquant exigera quelque chose pour gérer le retour du résultat plus tard: un callback, une promesse, un yield

]]>
http://sametmax.com/quelle-est-la-difference-entre-bloquer-et-en-cours-dexecution/feed/ 9