Distribution de billets de faveur: fils non freinés en Java. Métier à projet

Voulez-vous dans les threads Java qui ne mangent pas de mémoire comme s'ils n'étaient pas en eux-mêmes et ne ralentissent pas? Bon crédit, et ce problème répond à cette question.


Nous expliquons le travail de Project Loom sur les boîtes à pizza! Allez!


Tout cela est supprimé et écrit spécifiquement pour Habr .





Indicatifs d'appel


Voyez-vous souvent cette image sur votre service Web: au début, tout allait bien, puis un million de Chinois sont venus vers vous, le service a fait un million de fils et s'est noyé en enfer?





Voulez-vous une si belle photo?





En d'autres termes, voulez-vous dans les threads Java qui ne mangent pas de mémoire mais ne sont pas en eux-mêmes et ne ralentissent pas? Bon crédit, et ce problème répond à cette question.


Nous allons, en fait, déballer le nouveau cadre . Rappelez-vous comment Wylsacom a déballé les iPhones? Certains ne se souviennent pas déjà des anciens commentateurs, mais pourquoi? Parce que Habr est un bastion traditionnel, et les vidéos payantes sont, désolées, les rayons de la diarrhée . Dans cet article, nous traiterons exclusivement du hardcore technique.


Tout d'abord, deux minutes pour les globes oculaires, l'avertissement et les autres déchets, qui doivent être dit. Vous pouvez l'ignorer si vous êtes trop paresseux.


Tout d'abord, tout ce qui est dit dans la vidéo est ma pensée personnelle, et cela n'a rien à voir avec l'employeur ou la glorieuse société Oracle, le gouvernement mondial des lézards et une fichue chose dans un mortier. J'écris même ceci à trois heures du matin pour qu'il soit clair que c'est mon initiative personnelle, mes ordures personnelles. Tous les matchs sont purement aléatoires.


Mais il y a un autre objectif bonus. Nous parlons constamment de coroutines à Kotlin. Récemment, il y a eu des entretiens avec Roma Elizarov , le dieu de Corutin, et avec Pasha Finkelstein , qui va leur écrire des backends. Bientôt, il y aura une interview d'Andrei Breslav - qui est le père de Kotlin. Et partout, le projet Loom est mentionné d'une manière ou d'une autre, car il s'agit d'un analogue de la coroutine. Et si vous ne savez pas ce qu'est Loom, vous pourriez devenir stupide en lisant ces interviews. Il y a des mecs sympas, ils discutent de choses sympas. Et vous y êtes, et vous n'êtes pas avec eux, schmuck. C'est vraiment stupide.


Ne faites pas ça, lisez ce qu'est Loom dans cet article, ou regardez cette vidéo plus loin, je vais tout vous expliquer.


Alors, quelle est la complication. Il y a un tel mec, Ron Presler.






L'année dernière, il est allé sur la liste de diffusion , a déclaré que les threads en Java étaient nuls et lui a suggéré d'exécuter le runtime et de le corriger. Et tout le monde se moquait de lui et jetait des pierres, de la merde, sinon pour le fait qu'il avait écrit Quasar plus tôt, et c'est en fait très cool. Vous pouvez jurer sur Quasar pendant longtemps, mais il semble être là et cela fonctionne, et dans l'ensemble, c'est probablement un exploit.


Il y a beaucoup de govnokodery qui ne font rien, dites-le. Eh bien, faites-le bien, je suis le même. Ou il y a des gens qui semblent être des ingénieurs sympas, mais en général dans l'inconscient, ils disent ceci: "En Java, vous devez améliorer les threads." Que faut-il améliorer? Quels sont les fils?


Les gens sont généralement trop paresseux pour penser.


Comme dans une blague:
Petka et Vasily Ivanovich volent dans un avion.
Vasily Ivanovich demande: - Petka, appareils?
Petka répond: - 200!
Vasily Ivanovich: - Et qu'en est-il de 200?
Petka: - Et les électroménagers?


Je vais raconter une histoire. J'étais en Ukraine ce printemps, nous avons pris l'avion depuis la Biélorussie (vous comprenez pourquoi c'est impossible directement depuis Saint-Pétersbourg). Et à la douane, nous nous sommes assis pendant environ deux heures, rien de moins. Les agents des douanes sont très gentils, ont demandé sérieusement si Java est une technologie obsolète. Des gens assis à proximité qui volent vers le même konf. Et je suis un type de conférencier, je dois jouer un coup de pied, me tenir debout et, comme prévu, parler sans vergogne de choses que je n'utilise pas du tout. Et en cours de route, il a parlé de la distribution JDK appelée Liberica, c'est un tel JDK pour le Raspberry Pi.


Et qu'en pensez-vous. Pas même six mois ne s'écoulent avant que les gens frappent sur mon chariot et disent que, écoutez, nous avons déposé une solution dans Liber sur la prod, et j'ai déjà un rapport à ce sujet au jfuture.by konf biélorusse. Telle est l'approche. Ce n'est pas un mauvais évangéliste, mais un mec, un ingénieur normal.


Soit dit en passant, nous aurons bientôt une conférence Joker 2018 , qui comprendra à la fois Andrei Breslav (évidemment en fouillant dans les coroutines), et Pasha Finkelshtein , et Josh Long peut être interrogé sur le soutien de Spring à Loom. Eh bien, et un tas d'experts éminents cool, allez!

Et maintenant, revenons aux discussions. Les gens essaient de se faire une idée à travers leurs deux neurones dégradés, comme la morve enroulée sur leur poing, et marmonnent: "En Java, les threads ne sont pas comme ça, en Java, les threads ne sont pas comme ça." Appareils! Quels appareils? C'est généralement l'enfer.


Et voici Presler, un mec normal et non dégradé, et au début, il fait une description sensée. Un an plus tard, j'ai vu une démo de travail. J'ai dit tout cela pour que vous compreniez qu'une description normale des problèmes, une documentation normale est un tel héroïsme d'un genre spécial. Et la démo est généralement de l'espace. C'est la première personne à avoir fait quoi que ce soit dans ce sens. Il en a le plus besoin.


Avec la démo, Presler a pris la parole lors de la conférence et a publié cette vidéo:



En fait, tout cet article est un examen de ce qui a été dit là-bas. Je ne prétends pas du tout à l'unicité de ce matériau, tout ce qui se trouve dans cet article a été inventé par Ron.


La discussion porte sur trois sujets douloureux:


  • Continations
  • Les fibres
  • Appels de queue

Probablement, il était tellement écoeuré en sciant Quasar et en se battant avec ses pépins qu'il n'y a pas de force - vous devez le mettre en phase d'exécution.


C'était il y a un an, et depuis lors, ils ont scié un prototype. Certains ont déjà perdu espoir de voir un jour une démo, mais il y a un mois, ils y ont donné naissance et ont montré ce qui est visible sur ce tweet .






Les trois sujets douloureux de cette démo sont soit directement dans le code, soit au moins moralement présents. Eh bien, oui, ils n'ont pas encore maîtrisé les appels de queue, mais ils le veulent.


Problème


Les utilisateurs mécontents, les développeurs d'applications, lorsqu'ils créent une API, sont obligés de choisir entre deux chaises. Les pics sont intégrés sur une chaise, les fleurs poussent sur l'autre. Et ni l'un ni l'autre ne nous conviennent.






Par exemple, si vous écrivez un service qui fonctionne de manière synchrone, il fonctionne très bien avec le code hérité, il est facile de déboguer et de surveiller les performances. Des problèmes surgiront avec la bande passante et l'évolutivité. Tout simplement parce que le nombre de threads que vous pouvez désormais exécuter sur un simple matériel, sur du matériel de base - disons, deux mille. C'est beaucoup moins que le nombre de connexions qui pourraient être ouvertes sur ce serveur. Ce qui du point de vue du netcode peut être presque sans fin.


(Eh bien, oui, cela a quelque chose à voir avec le fait que les sockets en Java sont arrangés idiotes, mais c'est un sujet pour une autre conversation)


Imaginez que vous écrivez une sorte de MMO.






Par exemple, pendant la guerre du Nord dans EVE Online, deux mille quatre cents pilotes se sont rassemblés à un point dans l'espace, chacun d'eux - conditionnellement, s'il était écrit en Java - ne serait pas un fil, mais plusieurs. Et le pilote, bien sûr, est une logique métier complexe, et non la publication d'un code HTML qui peut être exclu à la main dans une loupe.


Le temps de réponse dans cette bataille a été si long que le joueur a dû attendre quelques minutes pour attendre le tir. Pour autant que je sache, CCP spécifiquement pour cette bataille a jeté les énormes ressources matérielles de son cluster.


Bien que, je cite probablement EVE comme exemple en vain, car, pour autant que je comprends, tout est écrit en Python, et en Python avec multithreading, il est encore pire que le nôtre - et nous pouvons considérer une mauvaise concurrence des fonctionnalités du langage. Mais alors l'exemple est clair et avec des photos.


Si vous êtes intéressé par le sujet de l'OMI en général et l'histoire de la «guerre du Nord» en particulier, récemment une très bonne vidéo sur ce sujet est apparue sur la chaîne Bulzhat (quel que soit son nom), regardez depuis mon horodatage.



Nous revenons au sujet.


D'un autre côté, vous pouvez utiliser une sorte de framework asynchrone. Il est évolutif. Mais nous tomberons immédiatement dans un débogage très difficile, un profilage compliqué de la performance, nous ne pourrons pas l'intégrer de manière transparente avec l'héritage, vous devrez réécrire beaucoup de choses, les envelopper dans des emballages abominables et avoir généralement l'impression que nous venons d'être violés. Plusieurs fois de suite. Pendant des jours, en fait, tout le temps que nous écrivons ceci, vous devrez vous sentir comme ça.


J'ai demandé à l'expert, le célèbre académicien Escobar, ce qu'il en pensait:






Que faire? Les soi-disant faybers se précipitent à la rescousse.


Dans le cas général, les fibres sont de tels fils légers qui fouillent également dans l'espace d'adressage (car les miracles ne se produisent pas, vous comprenez). Mais contrairement aux threads ordinaires, ils n'utilisent pas le multitâche préemptif, mais le multitâche coopératif. En savoir plus sur Wikipedia .


La fibre peut réaliser les avantages de la programmation synchrone et asynchrone. Par conséquent, l'utilisation du fer est augmentée et nous utilisons moins de serveurs dans le cluster pour la même tâche. Eh bien, dans notre poche pour cela, nous obtenons de la lavande. Babos. Lave. L'argent. Eh bien, vous obtenez le point. Pour le serveur enregistré.


À la croisée des chemins


La première chose que je veux discuter. Les gens ne comprennent pas la différence entre les continuations et la fibre.
Maintenant, il y aura une illumination culte!


Nous allons annoncer le fait: la poursuite et la fibre sont deux choses différentes.


Continuations


Les fibres sont construites au sommet d'un mécanicien appelé Continuations.


Les continuations (plus précisément, les continuations délimitées) sont une sorte de calcul, d'exécution, un morceau de programme qui peut s'endormir, puis se réveiller et continuer l'exécution depuis l'endroit où il s'est endormi. Il peut même parfois être cloné ou sérialisé, même pendant qu'il dort.


J'utiliserai le mot «continuation», et non «continuation» (comme il est écrit sur Wikipédia), car nous communiquons tous en anglais . En utilisant la terminologie russe normale, on peut facilement arriver à une situation où la différence entre le terme russe et l'anglais devient trop grande et personne d'autre ne comprend le sens de ce qui a été dit.


J'utiliserai également parfois le mot «crowding out» au lieu de la version anglaise de «yield». Juste le mot "céder" - c'est une sorte de vraiment méchant. Il y aura donc «éviction».


Alors voilà. Il est très important qu'il n'y ait pas de concurrence dans le continuum. C'est en soi la primitive minimale de ce processus.


Vous pouvez considérer la continuation comme un Runnable , à l'intérieur duquel vous pouvez appeler la méthode pause() . C'est à l'intérieur et directement, car notre multitâche est coopératif. Et puis vous pouvez l'exécuter à nouveau, et au lieu de tout recalculer, il continuera là où il s'était arrêté. Ce genre de magie. Nous reviendrons à la magie.


Où obtenir une démo avec des suites de travail - nous discuterons à la toute fin. Parlons maintenant de ce qui existe.


La classe de continuation elle-même est située sur java.base, tous les liens seront dans la description. ( src/java.base/share/classes/java/lang/Continuation.java ). Mais cette classe est très grande, volumineuse, il est donc logique de ne regarder que quelque sorte de compression.


 public class Continuation implements Runnable { public Continuation(ContinuationScope scope, Runnable body); public final void run(); public static void yield(ContinuationScope scope); public boolean isDone(); protected void onPinned(Reason reason) { throw new IllegalStateException("Pinned: " + reason); } } 

Notez qu'en fait ce fichier est en constante évolution. Par exemple, à la veille, la poursuite n'a pas implémenté l'interface Runnable . Traitez cela comme une sorte d'esquisse.


Jetez un oeil au constructeur. body - c'est le code que vous essayez d'exécuter, et scope - est une sorte de skop qui vous permet d'imbriquer des continuations dans des continuations.


En conséquence, vous pouvez soit planifier ce code à la fin avec la méthode run , soit le remplacer par un tableau spécifique en utilisant la méthode yield (le tableau est nécessaire ici pour quelque chose comme le transfert d'actions aux gestionnaires imbriqués, mais nous ne nous soucions pas en tant qu'utilisateurs). Vous pouvez demander à l'aide de la méthode isDone si tout est terminé jusqu'à la fin.


Et pour des raisons dictées uniquement par les besoins de l'implémentation actuelle (mais très probablement, elle sera également incluse dans la version), il n'est pas toujours possible de faire du yield . Par exemple, si à l'intérieur de la suite nous avons eu une transition vers le code natif et qu'un cadre natif est apparu sur la pile, alors il est impossible de se coincer. Cela se produira également si vous essayez de vous évincer pendant qu'un moniteur natif, tel qu'une méthode synchronisée, est pris à l'intérieur du corps du continuum. Par défaut, lorsque vous essayez de simuler cela, une exception est levée ... mais les fibres construites au-dessus des suites surchargent cette méthode et font autre chose. Ce sera un peu plus tard.


Vous pouvez l'utiliser de la manière suivante:


 Continuation cont = new Continuation(SCOPE, () -> { while (true) { System.out.println("before"); Continuation.yield(SCOPE); System.out.println("after"); } }); while (!cont.isDone()) { cont.run(); } 

Ceci est un exemple de la présentation de Presler. Encore une fois, ce n'est pas un code "trivial", c'est une sorte de croquis.


Ceci est une esquisse de ce que nous faisons continuation, au milieu de cette continuation nous sommes évincés puis dans un cycle sans fin nous demandons si la continuation a fonctionné jusqu'au bout et si elle doit être poursuivie.


Mais en général, il n'est pas prévu que les programmeurs d'applications ordinaires se rapportent à cette API. Il est destiné aux créateurs de frameworks système. Les frameworks de formation de systèmes comme le Spring Framework adopteront immédiatement cette fonctionnalité dès sa sortie. Tu verras. Considérez ceci comme une prédiction. Une telle prédiction légère, car tout est assez évident ici. Toutes les données de prédiction sont. Cette fonctionnalité est trop importante pour ne pas s'adapter. Par conséquent, il n'est pas nécessaire de s'inquiéter à l'avance que quelqu'un vous torture avec l'encodage sous cette forme. Eh bien, si vous êtes un développeur Spring, vous saviez ce que vous faisiez.


Et maintenant, en plus des suites, des fibres sont construites.


Les fibres


Alors, ce qui dans notre cas signifie fibre.

C'est une sorte d'abstraction, qui est:


  • Threads légers traités dans la JVM elle-même, et non dans le système d'exploitation;
  • Avec des frais généraux extrêmement bas pour créer, maintenir la vie, changer de tâche;
  • Qui peut être exécuté des millions de fois.

De nombreuses technologies tentent de fabriquer la fibre d'une manière ou d'une autre. Par exemple, dans Kotlin, il existe des coroutines implémentées sur une génération de bytecode très intelligente. TRÈS INTELLIGENT . Mais le runtime est un meilleur endroit pour implémenter de telles choses.


Au minimum, la JVM sait déjà bien gérer les threads, et tout ce que nous devons faire est de rationaliser le processus de codage pour le multithreading. Vous pouvez utiliser des API asynchrones, mais cela peut difficilement être appelé «simplification»: même en utilisant des choses comme Reactor , Spring Project Reactor, qui permettent d'écrire du code apparemment linéaire, ne vous sera d'aucune utilité si vous devez déboguer des problèmes complexes.


Donc fibre.


La fibre se compose de deux composants. C’est:


  • Continuation
  • Planificateur

Soit:


  • Continuation
  • Planificateur





Vous pouvez décider qui est le planificateur ici. Je pense que le planificateur ici est Jay.


  • Fibre encapsule le code que vous souhaitez exécuter dans une continuation
  • Le planificateur les lance sur un pool de threads de support

Je les appellerai des fils de support.







Le prototype actuel utilise java.util.concurrent.Executor et le planificateur ForkJoinPool . Nous avons tout. À l'avenir, quelque chose de plus intelligent pourrait y apparaître, mais pour l'instant, comme ça.


Comment se comporte la suite:


  • Il est évincé (rendement) lorsqu'un verrouillage se produit (par exemple, sur IO);
  • Continue lorsque vous êtes prêt à continuer (par exemple, l'opération d'E / S est terminée et vous pouvez continuer).

État actuel des travaux:


  • L'accent principal sur la philosophie, les concepts;
  • L'API n'est pas fixe, c'est "pour le show". Ceci est un prototype de recherche;
  • Il existe un prototype de travail codé prêt à l'emploi de la classe java.lang.Fiber .

Il en sera question.


Ce qui a déjà été scié dans la fibre:


  • Il exécute un lancement de tâche;
  • Parking voiture non garé sur un transporteur;
  • En attente de l'achèvement de la fibre.

Schéma du circuit


 mount(); try { cont.run(); } finally () { unmount(); } 

  • Nous pouvons monter une fibre sur un porte-fil;
  • Exécutez ensuite la suite;
  • Et attendez qu'elle soit évincée ou qu'elle s'arrête honnêtement;
  • Au final, on laisse toujours le fil.

Ce pseudo-code sera exécuté sur le ForkJoinPool ou sur un autre (qui finira par être dans la version finale).


Utilisation en réalité


 Fiber f = Fiber.execute( () -> { System.out.println("Good Morning!"); readLock.lock(); try { System.out.println("Good Afternoon"); } finally { readLock.unlock(); } System.out.println("Good Night"); }); 

Regardez, nous créons une fibre dans laquelle:


  • bienvenue à tous;
  • nous bloquons le loke réentrant;
  • à votre retour, félicitations pour votre déjeuner;
  • libérer enfin le verrou;
  • et dis au revoir.

Tout est très simple.


Nous n'engendrons pas directement le surpeuplement. Project Loom lui-même sait que lorsque readLock.lock(); déclenché readLock.lock(); il devrait intervenir et faire implicitement de la répression. L'utilisateur ne voit pas cela, mais cela se produit là-bas.


Des piles, des piles partout!


Montrons ce qui se passe en utilisant la pile de pizza comme exemple.


Au début, le thread porteur est dans un état d'attente et rien ne se produit.





Haut de la pile en haut, rappelez-vous.


L'exécution de la fibre a ensuite été planifiée et la tâche de fibre a commencé à s'exécuter.





En lui-même, il lance évidemment une suite, dans laquelle le vrai code est déjà localisé.





Du point de vue de l'utilisateur, nous n'avons encore rien lancé ici.


Ce n'est que la première image du code utilisateur apparue sur la pile, et elle est marquée en violet.


En outre, le code est exécuté, exécuté, à un moment donné, la tâche tente de capturer le verrou et de le bloquer, ce qui conduit à un éviction automatique.





Tout ce qui se trouve sur la pile de continuation est stocké dans un certain endroit magique. Et disparaît.





Comme vous pouvez le voir, le flux revient à la fibre, à l'instruction qui suit Continuation.run . Et c'est la fin du code fibre.


La tâche de fibre se termine, le support média attend un nouveau travail.





La fibre est garée, quelque part se trouve, le continuum est complètement évincé.


Tôt ou tard, le moment vient où celui qui possède la serrure la libère.
Cela conduit au fait que la fibre, qui attendait la libération du verrou, est déballée. La tâche de cette fibre recommence.


  • Reentrantlock.unlock
  • Locksupport.unpark
  • Fiber.unpark
  • ForkJoinPool.execute

Et nous retournons rapidement à la pile, qui était récemment.





De plus, le fil porteur peut être complètement différent. Et cela a du sens!


Exécutez à nouveau la suite.





Et voici la MAGIE !!! La pile est restaurée et l'exécution se poursuit avec l'instruction après Continuation.yield .





Nous rampons hors du verrou juste parqué et commençons à exécuter tout le code restant dans la suite:





La tâche utilisateur se termine et le contrôle revient à la tâche fibre immédiatement après l'instruction continuation.run





Dans le même temps, l'exécution de la fibre se termine, et nous nous retrouvons à nouveau en mode veille.





Le prochain lancement de la fibre amorce à nouveau tout le cycle de renaissances décrit ci-dessus.





Exemples en direct


Et qui a déjà dit que tout cela fonctionne? S'agit-il de quelques microbenchmarks écrits au cours de la soirée?


À titre d'exemple du fonctionnement des pétards, les Oraklovites ont écrit un petit serveur Web et l'ont alimenté en requêtes pour qu'il s'étouffe. Ensuite, ils ont été transférés sur fibre. Le serveur a cessé de s'étouffer et nous en avons conclu que les fibres fonctionnent.


Je n'ai pas le code exact pour ce serveur, mais si cet article obtient suffisamment de likes et de commentaires, j'essaierai d'écrire un exemple moi-même et de créer de vrais graphiques.


Les problèmes


Y a-t-il des problèmes ici? Oui bien sûr! Toute l'histoire des faybers est une histoire de problèmes et de compromis continus.


Problèmes philosophiques


  • Faut-il réinventer les fils?
  • Tout le code existant devrait-il fonctionner correctement à l'intérieur de la fibre?

Le prototype actuel fonctionne avec des limitations. Ce qui peut entrer dans la version, bien que je ne le veuille pas. Pourtant, OpenJDK est une chose qui respecte la compatibilité sans fin.


Quelles sont les limitations techniques? Les limitations les plus évidentes sont 2 pièces.


Le problème est une fois - vous ne pouvez pas remplacer les cadres natifs


 PrivilegedAction<Void> pa = () -> { readLock.lock(); // may park/yield try { // } finally { readLock.unlock(); } return null; } AccessController.doPrivileged(pa); //native method 

Ici, doPrivileged appelle la méthode native.


Vous appelez doPrivileged , sautez hors de la machine virtuelle, un cadre natif apparaît sur votre pile, après quoi vous essayez de vous garer sur la ligne readLock.lock() . Et à ce moment-là, le fil porteur sera taché jusqu'à ce qu'il soit décroché. Autrement dit, le fil disparaît. Dans ce cas, les fils porteurs peuvent se terminer, et en général, cela rompt toute l'idée de fibre.


La manière de résoudre ce problème est déjà connue et des discussions sont en cours à ce sujet.


Problème deux - blocs synchronisés


Ce sont des ordures beaucoup plus graves


 synchronized (object) { //may park object.wait(); //may park } 

 synchronized (object) { //may park socket.getInputStream().read(); //may park } 

En cas de capture du moniteur dans la fibre, les fils du support démarrent également.


Il est clair que dans un code complètement nouveau, vous pouvez changer les moniteurs en verrous directs, au lieu d'attendre + de notifier, vous pouvez utiliser des objets de condition, mais que faire de l'héritage? C'est un problème.


API de thread? Thread.currentThread ()? Thread locaux?


Dans le prototype actuel, Thread et Fiber ont créé une superclasse commune appelée Strand .


Cela vous permet de transférer l'API de la manière la plus minimale.
Que faire ensuite - comme toujours dans ce projet, est une question.


Que se passe-t-il avec l'API Thread maintenant?


  • La première utilisation de Thread.currentThread() dans une fibre crée une sorte de thread fantôme, Shadow Thread;
  • du point de vue du système, il s'agit d'un thread «non publié», et il n'y a aucune méta-information VM dedans;
  • ST essaie d'imiter tout ce qu'il peut;
  • mais vous devez comprendre que l'ancienne API a beaucoup de déchets;
  • plus spécifiquement, Shadow Thread implémente l'API Thread pour tout sauf stop , suspend , resume et gérer les exceptions non interceptées.

Que faire des sections locales de threads?


  • maintenant les sections locales de fil se transforment en sections locales de fibre;
  • il y a beaucoup de problèmes avec cela, tout cela est en discussion;
  • un ensemble d'utilisations est particulièrement discuté;
  • Les threads ont historiquement utilisé à la fois correctement et incorrectement (ceux qui utilisent incorrectement espèrent toujours quelque chose, et vous ne pouvez pas les décevoir complètement);
  • en général, cela crée toute une gamme d'applications:
    • Haut niveau: cache des connexions ou des mots de passe dans le conteneur;
    • Bas niveau: processeur dans les bibliothèques système.

Combien tout cela mange





Discussion:


  • Pile: 1 Mo et 16 Ko sur les structures de données du noyau;
  • Par instance de thread: 2300 octets, y compris les méta-informations VM.

Fibre:


  • Pile de continuation: de centaines d'octets à kilo-octets;
  • Par instance de fibre: 200-240 octets.

La différence est énorme!
Et c’est exactement ce qui permet à des millions de personnes de s’activer.


Que peut garer


Il est clair que la chose la plus magique est le stationnement automatique lorsque certains événements se produisent. Qu'est-ce qui est actuellement pris en charge?


  • Thread.sleep, rejoignez;
  • java.util.concurrent et LockSupport.lock;
  • IO: réseau sur sockets (socket lire, écrire, connecter, accepter), fichiers, tubes;
  • Tout cela n'est pas terminé, mais la lumière dans le tunnel est visible.

Communication entre fibre


Une autre question que tout le monde se pose est: comment échanger de manière compétitive des informations entre les fibres.


  • Le prototype actuel lance des tâches dans Runnable , peut être converti en CompletableFuture , si pour une raison quelconque vous en avez besoin;
  • java.util.concurrent "fonctionne juste." Vous pouvez tout tâtonner de manière standard;
  • il peut y avoir de nouvelles API pour le multithreading, mais ce n'est pas exact;
  • un tas de petites questions comme «les fibres devraient-elles renvoyer des valeurs?»; tout est discuté, ils ne sont pas dans le prototype.

Comment les suites sont-elles implémentées dans le prototype?


Des exigences évidentes sont imposées à la suite: vous devez utiliser le moins de RAM possible et vous devez basculer entre elles le plus rapidement possible. Sinon, cela ne fonctionnera pas pour les garder par millions. La tâche principale ici est en quelque sorte de ne pas faire une copie complète de la pile pour chaque parking-uppark. Et il y a un tel schéma! Essayons d'expliquer cela dans les images.


La façon la plus cool serait, bien sûr, de simplement mettre toutes les piles sur une hanche java et de les utiliser directement. Mais il n'est pas clair comment coder maintenant, donc le prototype utilise la copie. Mais copier avec un petit mais important hack.


Nous avons deux chaises ... Je veux dire, deux piles. Deux tableaux java dans la hanche. L'un est un tableau d'objets, où nous stockons les références aux objets. Le second est primitif (par exemple, intime), qui gérera tout le reste.





Nous sommes maintenant dans un état où la poursuite est sur le point d'être effectuée pour la toute première fois.


run appelle une méthode interne appelée enter :





Et puis le code utilisateur est exécuté, jusqu'au premier éviction.





À ce stade, un appel VM est effectué, ce qui freeze appels. Dans ce prototype, cela se fait directement physiquement - en utilisant la copie.





Nous commençons le processus de copie séquentielle des images de la pile native vers java hip.





Il est nécessaire de vérifier si les moniteurs y sont maintenus ou si le code natif est utilisé, ou autre chose qui ne nous permettra vraiment pas de continuer à travailler.





Et si tout va bien, nous copions d'abord dans un tableau primitif:





Ensuite, nous isolons les références aux objets et les enregistrons dans le tableau d'objets:





En fait, deux thés à tous ceux qui ont lu cet endroit!


De plus, nous continuons cette procédure pour tous les autres éléments de la pile native.





Hourra! Nous avons tout copié dans le nid dans la hanche. Vous pouvez sauter en toute sécurité sur le lieu de l'appel sans craindre que nous ayons perdu quelque chose. Tout est branché.





Tôt ou tard, le code appelant appellera à nouveau notre continuation. Et elle doit continuer de l'endroit où elle a été laissée la dernière fois. Telle est notre tâche.





Vérifier si la continuation était en cours, dit oui, elle était en cours. Donc, vous devez appeler VM, nettoyer de l'espace sur la pile et appeler la fonction interne de thaw VM. «Dégel» est traduit en russe par «dégel», «dégeler», ce qui semble assez logique. Il est nécessaire de libérer les images de la pile de continuation dans notre pile native principale.


Je ne suis pas sûr que le dégivrage du thé soit assez clair. La mauvaise abstraction est comme un chaton avec une porte. Mais cela fera l'affaire pour nous.





Nous faisons des copies assez évidentes.


Tout d'abord avec un tableau primitif:





Puis à partir du lien:





Vous devez patcher un peu le copié pour obtenir la pile correcte:





Répétez l'obscénité pour toutes les images:





Vous pouvez maintenant revenir en arrière et continuer comme si de rien n'était.





Le problème est qu'une copie complète de la pile n'est pas du tout ce que nous aimerions avoir. C'est très inhibiteur. Tout cela est l'isolement des liens, vérifie l'épinglage, ce n'est pas rapide. Et surtout - tout cela dépend linéairement de la taille de la pile! En un mot, l'enfer. Pas besoin de faire ça.


Au lieu de cela, nous avons une autre idée - la copie paresseuse.


Revenons à l'endroit où nous avons déjà une continuation figée.





Nous continuons le processus comme auparavant:





De la même manière que précédemment, nous nettoyons l'endroit sur la pile native:





Mais nous ne copions pas tout de suite, mais seulement une ou deux images:





Maintenant, le hack. Vous devez patcher l'adresse de retour de la méthode C afin qu'elle pointe vers une certaine barrière de retour:





Maintenant, vous pouvez revenir en toute sécurité au yield :





Ce qui à son tour conduira à un appel au code utilisateur dans la méthode C :





Imaginez maintenant que C veut revenir au code qui l'a appelé. Mais son invocateur est B , et il n'est pas sur la pile! Par conséquent, lorsqu'il tentera de revenir, il ira à l'adresse de retour, et cette adresse est maintenant la barrière de retour. Et, vous savez, cela attirera à nouveau un appel de thaw :





Et le thaw libérera la prochaine image sur la pile de continuation, et c'est B :





En fait, nous l'avons copié paresseusement, sur demande.


Ensuite, nous supprimons B de la pile de continuation et définissons à nouveau la barrière (la barrière doit être définie car il reste quelque chose sur la pile de continuation). Et ainsi de suite.





Mais supposons que B ne revienne pas au code appelant, mais appelle d'abord une autre méthode D Et cette nouvelle méthode veut également être évincée.





Dans ce cas, lorsque viendra le temps de freeze , nous n'aurons besoin de copier que le haut de la pile native sur la pile de continuation:





Ainsi, la quantité de travail effectuée ne dépend pas linéairement de la taille de la pile. Cela dépend linéairement uniquement du nombre d'images que nous avons réellement utilisées dans le travail.


Que reste-t-il?


Les développeurs gardent à l'esprit certaines fonctionnalités, mais ils ne sont pas entrés dans le prototype.


  • Sérialisation et clonage. La possibilité de continuer sur une autre machine, à un autre moment, etc.
  • JVM TI et débogage, comme s'il s'agissait de threads normaux. Si vous êtes bloqué à la lecture de la prise, vous ne verrez pas un beau saut de rendement, dans le prototype le fil sera simplement bloqué, comme tout autre fil ordinaire.
  • La récursivité de la queue n'a même pas été touchée.

Prochaines étapes:


  • Créez une API humaine;
  • Ajoutez toutes les fonctionnalités manquantes;
  • Améliorez les performances.

Où trouver


Le prototype est réalisé sous forme de brunch dans le référentiel OpenJDK. Vous pouvez télécharger le prototype ici en passant aux fibers brunch.


C'est fait comme ceci:


 $ hg clone http://hg.openjdk.java.net/loom/loom $ cd loom $ hg update -r fibers $ sh configure $ make images 

Comme vous le savez, tout cela va commencer l'assemblage de ce putain d'OpenJDK. Par conséquent, premièrement, la prochaine demi-heure de votre vie devra faire autre chose, pendant que tout cela se passe.





Deuxièmement, vous devez disposer d'un ordinateur correctement configuré avec une chaîne d'outils C ++ et des bibliothèques GNU. Je laisse entendre qu'il n'est pas recommandé de le faire sous Windows. Sérieusement, même en téléchargeant VirtualBox et en y installant un nouvel Ubuntu, vous passerez des ordres de grandeur moins de temps que d'essayer de réaliser une autre erreur inhumaine lors de la construction à partir de Cygwin ou msys64. C'est là que msys est encore pire que Cygwin.


Bien que ce ne soit, bien sûr, qu'un mensonge, je me lasse de vous écrire des instructions de montage .


- , mercurial extension fsmonitor. , , hg help -e fsmonitor .
~/.hgrc :


 [fsmonitor] mode = on 

- . -, cp -R ./loom ./loom-backup .


, . , Java- , .


sh configure - . , Ubuntu, Autoconf ( sudo apt-get install autoconf ). — OpenJDK Ubuntu, , . Windows , .


, , hg diff --stat -r default:fibers .


, , , .


Conclusion


«, ». «», . «Loom» — « ». Project Loom .


, . , «» , , — , , , — .


, , XIX , .




. -, .


, . IDE .


, , , « », «», « » .


? . .


Je vous remercie

Source: https://habr.com/ru/post/fr422519/


All Articles