Möchten Sie in Java-Threads, die nicht Speicher essen, als ob sie nicht in sich selbst sind und nicht verlangsamen? Gute Kreditwürdigkeit, und dieses Problem beantwortet diese Frage.
Wir erklären die Arbeit von Project Loom an Pizzaschachteln! Komm schon!
All dies wird entfernt und speziell für Habr geschrieben .
Rufzeichen
Sehen Sie dieses Bild oft in Ihrem Webdienst: Zuerst war alles in Ordnung, dann kamen eine Million Chinesen zu Ihnen, der Dienst machte eine Million Fäden und ertrank in der Hölle?

Willst du so ein schönes Bild?

Mit anderen Worten, möchten Sie in Java-Threads, die kein Gedächtnis fressen, aber nicht an sich sind und nicht langsamer werden? Gute Kreditwürdigkeit, und dieses Problem beantwortet diese Frage.
Wir werden in der Tat damit beschäftigt sein, das neue Framework auszupacken . Erinnerst du dich, wie Wylsacom iPhones ausgepackt hat? Einige erinnern sich noch nicht an die alten Rezensenten, aber warum? Weil Habr eine Hochburg des Mainstreams ist und bezahlte Videos leider die Strahlen des Durchfalls sind . In diesem Beitrag beschäftigen wir uns ausschließlich mit technischem Hardcore.
Zunächst zwei Minuten zu den Augäpfeln, Haftungsausschluss und anderem Müll, der gesagt werden muss. Sie können es überspringen, wenn Sie zu faul sind.
Erstens sind alles, was in dem Video gesagt wurde, meine persönlichen Gedanken, und sie haben nichts mit dem Arbeitgeber oder dem glorreichen Oracle-Unternehmen, der Weltregierung der Eidechsen und einer verdammten Sache im Mörser zu tun. Ich schreibe das sogar um drei Uhr morgens, damit klar ist, dass es meine persönliche Initiative ist, mein persönlicher Müll. Alle Übereinstimmungen sind rein zufällig.
Aber es gibt noch ein anderes Bonusziel. Wir reden ständig über Coroutinen in Kotlin. Kürzlich gab es Interviews mit Roma Elizarov , dem Gott von Corutin, und mit Pascha Finkelstein , der Backends darüber schreiben wird. Bald wird es ein Interview mit Andrei Breslav geben - dem Vater von Kotlin. Und überall wird das Loom-Projekt auf die eine oder andere Weise erwähnt, weil es ein Analogon von Coroutine ist. Und wenn Sie nicht wissen, was Loom ist, werden Sie beim Lesen dieser Interviews möglicherweise dumm. Es gibt einige coole Typen, die über coole Dinge diskutieren. Und da bist du, und du bist nicht bei ihnen, du Trottel. Das ist sehr dumm.
Tun Sie dies nicht, lesen Sie, was Loom in diesem Artikel ist, oder schauen Sie sich dieses Video weiter an, ich werde alles erklären.
Also, was ist die Komplikation. Es gibt so einen Kerl, Ron Presler.

Letztes Jahr ging er zur Mailingliste , sagte, dass Threads in Java scheiße sind, und schlug vor, die Laufzeit auszuführen und zu reparieren. Und jeder würde ihn auslachen und Steine werfen, Scheiße, wenn nicht die Tatsache, dass er Quasar früher geschrieben hat, und das ist eigentlich sehr cool. Sie können bei Quasar lange schwören, aber es scheint da zu sein und es funktioniert, und im Großen und Ganzen ist dies eher eine Errungenschaft.
Es gibt verdammt viele Govnokodery, die nichts tun, sagen Sie einfach. Nun, mach es richtig, ich bin das gleiche. Oder es gibt Leute, die coole Ingenieure zu sein scheinen, aber im Allgemeinen sagen sie im Unbewussten Folgendes: "In Java müssen Sie die Threads verbessern." Was kann man verbessern? Was sind Threads?
Die Leute sind im Allgemeinen zu faul zum Nachdenken.
Wie in einem Witz:
Petka und Vasily Ivanovich fliegen in einem Flugzeug.
Wassili Iwanowitsch fragt: - Petka, Geräte?
Petka antwortet: - 200!
Wassili Iwanowitsch: - Und was ist mit 200?
Petka: - Was ist mit Geräten?
Ich werde eine Geschichte erzählen. Ich war diesen Frühling in der Ukraine, wir sind aus Weißrussland geflogen (Sie verstehen, warum es direkt aus St. Petersburg unmöglich ist). Und beim Zoll saßen wir ungefähr zwei Stunden, nicht weniger. Zollbeamte sind sehr nett, in aller Ernsthaftigkeit gefragt, ob Java eine veraltete Technologie ist. In der Nähe saßen Leute, die zum selben Konf fliegen. Und ich bin eine Art Redner, ich muss stechen, stehen und wie erwartet schamlos über Dinge sprechen, die ich überhaupt nicht benutze. Und auf dem Weg sprach er über die JDK-Distribution namens Liberica, dies ist eine solche JDK für den Raspberry Pi.
Und was denkst du? Es vergehen nicht einmal sechs Monate, bis Leute auf meinen Wagen klopfen und sagen, dass wir in Liber eine Lösung für das Produkt gefunden haben, und ich habe bereits einen Bericht darüber in der belarussischen jfuture.by konf. Dies ist der Ansatz. Dies ist kein mieser Evangelist, sondern ein Typ, ein normaler Ingenieur.
Übrigens werden wir bald eine Joker 2018-Konferenz haben , an der sowohl Andrei Breslav (der offensichtlich in Coroutinen stöbert ) als auch Pasha Finkelshtein und Josh Long nach Spring's Unterstützung für Loom gefragt werden. Nun, und ein paar coole prominente Experten, komm schon!
Und jetzt zurück zu den Fäden. Die Leute versuchen, einen Gedanken durch ihre degradierten zwei Neuronen zu ziehen, so einen gewundenen Rotz auf ihrer Faust, und murmeln: "In Java sind Threads nicht so, in Java sind Threads nicht so." Geräte! Welche Geräte? Das ist im Allgemeinen die Hölle.
Und hier kommt Presler, ein normaler, nicht erniedrigter Typ, und zuerst macht er eine vernünftige Beschreibung. Ein Jahr später sägte ich eine funktionierende Demo. Ich habe das alles gesagt, damit Sie verstehen, dass eine normale Beschreibung von Problemen, eine normale Dokumentation ein solcher Heldentum der besonderen Art ist. Und die Demo ist im Allgemeinen Raum. Dies ist die erste Person, die tatsächlich etwas in diese Richtung getan hat. Er braucht am meisten.
Zusammen mit der Demo sprach Presler auf der Konferenz und veröffentlichte dieses Video:
In der Tat ist dieser gesamte Artikel eine Überprüfung dessen, was dort gesagt wurde. Ich behaupte überhaupt nicht für die Einzigartigkeit dieses Materials, alles, was in diesem Artikel steht, wurde von Ron erfunden.
Die Diskussion handelt von drei schmerzhaften Themen:
- Ansteckungen
- Fasern
- Schwanz ruft
Wahrscheinlich war er so krank, als er Quasar sägte und mit seinen Pannen kämpfte, dass es keine Kraft gibt - Sie müssen es in die Laufzeit schieben.
Es war vor einem Jahr und seitdem sägen sie einen Prototyp. Einige haben bereits die Hoffnung verloren, dass wir eines Tages eine Demo sehen werden, aber vor einem Monat haben sie sie geboren und gezeigt, was auf diesem Tweet sichtbar ist.

Alle drei schmerzhaften Themen in dieser Demo sind entweder direkt im Code enthalten oder zumindest moralisch präsent. Nun ja, sie haben Tail Calls noch nicht gemeistert, aber sie wollen.
Problem
Unglückliche Benutzer, Anwendungsentwickler, müssen bei der Erstellung einer API zwischen zwei Lehrstühlen wählen. Auf einem Stuhl sind Spitzen eingebaut, auf dem anderen wachsen Blumen. Und weder der eine noch der andere passt zu uns.

Wenn Sie beispielsweise einen Dienst schreiben, der synchron funktioniert, funktioniert er hervorragend mit Legacy-Code. Das Debuggen und Überwachen der Leistung ist einfach. Probleme treten bei der Bandbreite und Skalierbarkeit auf. Nur weil die Anzahl der Threads, die Sie jetzt auf einer einfachen Hardware ausführen können, auf Standardhardware - sagen wir mal zweitausend. Dies ist viel weniger als die Anzahl der Verbindungen, die zu diesem Server geöffnet werden könnten. Was aus Sicht des Netcodes nahezu endlos sein kann.
(Nun ja, es hat etwas damit zu tun, dass die Sockets in Java schwachsinnig angeordnet sind, aber dies ist ein Thema für ein anderes Gespräch.)
Stellen Sie sich vor, Sie schreiben eine Art MMO.

Während des Nordischen Krieges in EVE Online versammelten sich beispielsweise zweitausendvierhundert Piloten an einem Punkt im Weltraum. Jeder von ihnen - bedingt, wenn er in Java geschrieben wurde - wäre nicht ein Thread, sondern mehrere. Und der Pilot ist natürlich eine komplexe Geschäftslogik und nicht die Ausgabe von HTML, das von Hand in einer Lupe ausgeschlossen werden kann.
Die Reaktionszeit in diesem Kampf war so lang, dass der Spieler einige Minuten warten musste, um auf den Schuss zu warten. Soweit ich weiß, warf die KPCh speziell für diesen Kampf die riesigen Hardwareressourcen ihres Clusters.
Obwohl ich EVE wahrscheinlich vergeblich als Beispiel anführe, weil meines Wissens alles in Python geschrieben ist und in Python mit Multithreading immer noch schlechter als bei uns - und wir können eine schlechte Konkurrenz der Merkmale der Sprache in Betracht ziehen. Aber dann ist das Beispiel klar und mit Bildern.
Wenn Sie sich für das Thema IMO im Allgemeinen und die Geschichte des „Nordischen Krieges“ im Besonderen interessieren, ist kürzlich ein sehr gutes Video zu diesem Thema auf dem Bulzhat-Kanal erschienen (was auch immer dieser Name bedeutet), schauen Sie sich meinen Zeitstempel an.
Wir kehren zum Thema zurück.
Auf der anderen Seite können Sie eine Art asynchrones Framework verwenden. Es ist skalierbar. Aber wir werden sofort in ein sehr schwieriges Debugging geraten, ein kompliziertes Profiling der Performance, wir werden es nicht nahtlos in das Erbe integrieren können, Sie müssen viele Dinge neu schreiben, sie in abscheuliche Wrapper einwickeln und im Allgemeinen das Gefühl haben, nur vergewaltigt worden zu sein. Mehrmals hintereinander. Tatsächlich müssen Sie sich tagelang die ganze Zeit, in der wir dies schreiben, so fühlen.
Ich habe den Experten, den berühmten Akademiker Escobar, gefragt, was er davon hält:

Was tun? Die sogenannten Fasern haben es eilig zu helfen.
Im allgemeinen Fall sind Fasern solche leichten Fäden, die auch durch den Adressraum stöbern (weil Wunder nicht geschehen, verstehen Sie). Im Gegensatz zu normalen Threads verwenden sie jedoch kein präemptives Multitasking, sondern kooperatives Multitasking. Lesen Sie mehr auf Wikipedia .
Fiber kann die Vorteile sowohl der synchronen als auch der asynchronen Programmierung nutzen. Infolgedessen wird die Verwendung von Eisen erhöht, und wir verwenden weniger Server im Cluster für dieselbe Aufgabe. Nun, in unserer Tasche dafür bekommen wir Lavendel. Babos. Lave. Geld. Nun, du verstehst, worum es geht. Für den gespeicherten Server.
An der Kreuzung
Das erste, was ich diskutieren möchte. Die Menschen verstehen den Unterschied zwischen Fortsetzungen und Faser nicht.
Jetzt wird es eine Kult-Erleuchtung geben!
Wir werden die Tatsache bekannt geben: Fortsetzung und Faser sind zwei verschiedene Dinge.
Fortsetzung
Die Fasern bauen auf einem Mechaniker namens Continuations auf.
Fortsetzungen (genauer gesagt abgegrenzte Fortsetzungen) sind eine Art Berechnung, Ausführung, ein Teil eines Programms, das einschlafen, dann aufwachen und die Ausführung an dem Ort fortsetzen kann, an dem es eingeschlafen ist. Es kann manchmal sogar geklont oder serialisiert werden, selbst wenn er schläft.
Ich werde das Wort "Fortsetzung" verwenden und nicht "Fortsetzung" (wie es auf Wikipedia geschrieben steht), weil wir alle im Rubel kommunizieren . Mit normaler russischer Terminologie kann man leicht zu einer Situation kommen, in der der Unterschied zwischen dem russischen und dem englischen Begriff zu groß wird und niemand sonst die Bedeutung des Gesagten versteht.
Ich werde auch manchmal das Wort "Verdrängung" anstelle der englischen Version von "Ertrag" verwenden. Nur das Wort "Ertrag" - es ist eine Art wirklich böse. Daher wird es "Verdrängung" geben.
Also. Es ist sehr wichtig, dass es innerhalb des Kontinuums keinen Wettbewerb gibt. Es an sich ist das minimale Primitiv dieses Prozesses.
Sie können sich Continuation als Runnable
, in dem Sie die pause()
-Methode aufrufen können. Es ist innen und direkt, weil unser Multitasking kooperativ ist. Und dann können Sie es erneut ausführen, und anstatt alles neu zu berechnen, fährt er dort fort, wo er aufgehört hat. Diese Art von Magie. Wir werden zur Magie zurückkehren.
Wo Sie eine Demo mit Arbeitsfortsetzungen erhalten können, werden wir am Ende besprechen. Sprechen wir jetzt darüber, was da ist.
Die Fortsetzungsklasse selbst befindet sich in java.base, alle Links befinden sich in der Beschreibung. ( src/java.base/share/classes/java/lang/Continuation.java
). Aber diese Klasse ist sehr groß und umfangreich, daher ist es sinnvoll, nur eine Art Druck darauf zu betrachten.
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); } }
Beachten Sie, dass sich diese Datei ständig ändert. Zum Beispiel hat die Fortsetzung am Runnable
die Runnable
Schnittstelle nicht implementiert. Behandle dies als eine Art Skizze.
Schauen Sie sich den Konstruktor an. body
- dies ist der Code, den Sie ausführen möchten, und scope
- ist eine Art Skop, mit dem Sie Fortsetzungen in Fortsetzungen verschachteln können.
Dementsprechend können Sie diesen Code entweder mit der run
bis zum Ende vorplanen oder ihn mithilfe der yield
Methode durch ein bestimmtes Array ersetzen (das Array wird hier für die Weiterleitung von Aktionen an verschachtelte Handler benötigt, ist uns aber als Benutzer egal). Sie können mit der isDone
Methode fragen, ob alles bis zum Ende abgeschlossen ist.
Und aus Gründen, die ausschließlich von den Anforderungen der aktuellen Implementierung abhängen (aber höchstwahrscheinlich auch in der Version enthalten sein werden), ist es nicht immer möglich, eine yield
zu yield
. Wenn wir beispielsweise innerhalb der Fortsetzung einen Übergang zum nativen Code hatten und ein nativer Frame auf dem Stapel erschien, ist es unmöglich, stecken zu bleiben. Dies ist auch der Fall, wenn Sie versuchen, herausgedrückt zu werden, während ein nativer Monitor, z. B. eine synchronisierte Methode, in den Körper des Kontinuums aufgenommen wird. Wenn Sie versuchen, dies zu fälschen, wird standardmäßig eine Ausnahme ausgelöst ... aber die auf den Fortsetzungen aufgebauten Fasern überladen diese Methode und führen etwas anderes aus. Dies wird etwas später sein.
Sie können dies ungefähr folgendermaßen verwenden:
Continuation cont = new Continuation(SCOPE, () -> { while (true) { System.out.println("before"); Continuation.yield(SCOPE); System.out.println("after"); } }); while (!cont.isDone()) { cont.run(); }
Dies ist ein Beispiel aus Preslers Präsentation. Auch dies ist kein "trivialer" Code, sondern eine Art Skizze.
Dies ist eine Skizze dessen, was wir fortsetzen. In der Mitte dieser Fortsetzung sind wir verdrängt und dann fragen wir in einem endlosen Zyklus, ob die Fortsetzung bis zum Ende funktioniert hat und ob sie fortgesetzt werden sollte.
Im Allgemeinen ist jedoch nicht beabsichtigt, dass sich gewöhnliche Anwendungsprogrammierer auf diese API beziehen. Es richtet sich an Entwickler von System-Frameworks. Systembildende Frameworks wie das Spring Framework werden diese Funktion sofort übernehmen, sobald sie herauskommt. Du wirst sehen. Betrachten Sie dies als Vorhersage. Eine solche Lichtvorhersage, weil hier alles ziemlich offensichtlich ist. Alle Daten zur Vorhersage sind. Diese Funktion ist zu wichtig, um sie nicht anzupassen. Daher müssen Sie sich nicht im Voraus Sorgen machen, dass Sie jemand mit einer Kodierung in dieser Form quält. Wenn Sie ein Spring-Entwickler sind, wissen Sie, was Sie tun.
Und jetzt werden zusätzlich zu den Fortsetzungen Fasern gebaut.
Fasern
Also, was in unserem Fall Faser bedeutet.
Dies ist eine Art Abstraktion, die ist:
- Leichte Threads, die in der JVM selbst und nicht im Betriebssystem verarbeitet werden.
- Mit extrem geringem Overhead zum Erstellen, Aufrechterhalten des Lebens und Wechseln von Aufgaben;
- Welches kann millionenfach ausgeführt werden.
Viele Technologien versuchen auf die eine oder andere Weise, Fasern herzustellen. In Kotlin gibt es beispielsweise Coroutinen, die für eine sehr intelligente Bytecode-Generierung implementiert sind. Sehr intelligent . Aber Laufzeit ist ein besserer Ort, um solche Dinge zu implementieren.
Zumindest weiß die JVM bereits, wie man gut mit Threads umgeht, und wir müssen lediglich den Codierungsprozess für Multithreading optimieren. Sie können asynchrone APIs verwenden, dies kann jedoch kaum als „Vereinfachung“ bezeichnet werden: Selbst die Verwendung von Reactor und Spring Project Reactor, mit denen scheinbar linearer Code geschrieben werden kann, hilft nicht viel, wenn Sie komplexe Probleme beheben müssen.
Also Faser.
Faser besteht aus zwei Komponenten. Das:
Also:

Sie können entscheiden, wer der Planer hier ist. Ich denke, der Planer hier ist Jay.
- Fibre umschließt den Code, den Sie ausführen möchten, in einer Fortsetzung
- Scheduler startet sie in einem Pool von Carrier-Threads
Ich werde sie Trägerfäden nennen.

Der aktuelle Prototyp verwendet java.util.concurrent.Executor
und der integrierte Scheduler ForkJoinPool
. Wir haben alles. In Zukunft mag dort etwas Klügeres auftauchen, aber vorerst so.
Wie verhält sich die Fortsetzung:
- Es ist verdrängt (Ausbeute), wenn eine Sperre auftritt (z. B. bei E / A).
- Wird fortgesetzt, wenn Sie fortfahren möchten (z. B. ist der E / A-Vorgang abgeschlossen und Sie können fortfahren).
Aktueller Stand der Arbeiten:
- Der Schwerpunkt liegt auf Philosophie, Konzepten;
- Die API ist nicht festgelegt, sondern "for show". Dies ist ein Forschungsprototyp;
- Es gibt einen vorgefertigten codierten Arbeitsprototyp der Klasse
java.lang.Fiber
.
Es wird diskutiert.
Was wurde bereits in die Faser gesägt:
- Es wird ein Taskstart ausgeführt.
- Parkplatz parken auf einem Träger;
- Warten auf die Fertigstellung der Faser.
Schaltplan
mount(); try { cont.run(); } finally () { unmount(); }
- Wir können eine Faser auf einen Fadenträger montieren;
- Führen Sie dann die Fortsetzung aus.
- Und warten Sie, bis sie überfüllt ist oder ehrlich aufhört;
- Am Ende verlassen wir immer den Thread.
Dieser Pseudocode wird auf dem ForkJoinPool
oder auf einem anderen (der möglicherweise in der endgültigen Version enthalten sein wird) ausgeführt.
Verwendung in der Realität
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"); });
Schauen Sie, wir schaffen eine Faser, in der:
- begrüße alle;
- wir blockieren auf wiedereintrittsfähigem loke;
- Herzlichen Glückwunsch zu Ihrem Mittagessen.
- Lassen Sie endlich das Schloss los.
- und auf Wiedersehen sagen.
Alles ist sehr einfach.
Wir verursachen kein direktes Gedränge. Project Loom selbst weiß, dass readLock.lock();
ausgelöst wird readLock.lock();
er sollte eingreifen und implizit Unterdrückung tun. Der Benutzer sieht dies nicht, aber es passiert dort.
Stapel, Stapel überall!
Lassen Sie uns am Beispiel des Pizzastapels demonstrieren, was passiert.
Zu Beginn befindet sich der Träger-Thread in einem Wartezustand und es passiert nichts.

Oben auf dem Stapel oben, erinnern Sie sich.
Dann wurde die Ausführung der Glasfaser geplant, und die Glasfaseraufgabe wurde ausgeführt.

In sich selbst startet er offensichtlich eine Fortsetzung, in der sich der eigentliche Code bereits befindet.

Aus Sicht des Anwenders haben wir hier noch nichts gestartet.
Dies ist nur der erste Frame des Benutzercodes, der auf dem Stapel angezeigt wird, und er ist lila markiert.
Ferner wird der Code ausgeführt, ausgeführt, irgendwann versucht die Aufgabe, die Sperre zu erfassen und zu blockieren, was zu einer automatischen Verdrängung führt.

Alles, was sich auf dem Fortsetzungsstapel befindet, wird an einem bestimmten magischen Ort gespeichert. Und verschwindet.

Wie Sie sehen können, kehrt der Stream zur Faser zurück, zu der Anweisung, die auf Continuation.run
folgt. Und das ist das Ende des Glasfasercodes.
Die Glasfaseraufgabe endet, der Medienträger wartet auf einen neuen Auftrag.

Faser ist geparkt, irgendwo liegt, das Kontinuum ist völlig verdrängt.
Früher oder später kommt der Moment, in dem derjenige, dem das Schloss gehört, es freigibt.
Dies führt dazu, dass die Faser, die auf die Freigabe des Schlosses gewartet hat, ausgepackt wird. Die Aufgabe dieser Faser beginnt von vorne.
- Reentrantlock.unlock
- Locksupport.unpark
- Fiber.unpark
- ForkJoinPool.execute
Und wir kehren schnell zum Stapel zurück, der vor kurzem war.

Darüber hinaus kann das Trägergewinde völlig unterschiedlich sein. Und das macht Sinn!
Führen Sie die Fortsetzung erneut aus.

Und hier kommt die MAGIE !!! Der Stapel wird wiederhergestellt und die Ausführung mit der Anweisung nach Continuation.yield
fortgesetzt.

Wir kriechen aus dem gerade geparkten Schloss und beginnen mit der Ausführung des gesamten in der Fortsetzung verbleibenden Codes:

Die Benutzeraufgabe wird beendet, und die Steuerung kehrt unmittelbar nach der Anweisung continue.run zur Glasfaseraufgabe zurück

Gleichzeitig endet die Ausführung der Faser und wir befinden uns wieder im Standby-Modus.

Der nächste Start der Faser leitet erneut den gesamten oben beschriebenen Wiedergeburtszyklus ein.

Live-Beispiele
Und wer hat jemals gesagt, dass das alles funktioniert? Geht es um ein paar Mikrobenchmarks, die über den Abend geschrieben wurden?
Als Beispiel für den Betrieb der Feuerwerkskörper schrieben die Orakloviten einen kleinen Webserver und versorgten ihn mit Anfragen, damit er erstickte. Dann wurden sie auf Faser übertragen. Der Server hat aufgehört zu würgen, und daraus haben wir geschlossen, dass die Fasern funktionieren.
Ich habe nicht den genauen Code für diesen Server, aber wenn dieser Beitrag genügend Likes und Kommentare enthält, werde ich versuchen, selbst ein Beispiel zu schreiben und echte Grafiken zu erstellen.
Die Probleme
Gibt es hier irgendwelche Probleme? Ja natürlich! Die ganze Geschichte mit Faybers ist eine Geschichte über ständige Probleme und Kompromisse.
Philosophische Probleme
- Müssen wir Threads neu erfinden?
- Sollte der gesamte vorhandene Code in der Glasfaser ordnungsgemäß funktionieren?
Der aktuelle Prototyp läuft mit Einschränkungen. Welches kann in die Veröffentlichung gehen, obwohl ich nicht möchte. Trotzdem ist OpenJDK eine Sache, die endlose Kompatibilität respektiert.
Was sind die technischen Einschränkungen? Die offensichtlichsten Einschränkungen sind 2 Stück.
Das Problem ist einmal - Sie können native Frames nicht ersetzen
PrivilegedAction<Void> pa = () -> { readLock.lock();
Hier ruft doPrivileged die native Methode auf.
Sie rufen doPrivileged
, springen aus der VM heraus, ein nativer Frame wird auf Ihrem Stapel angezeigt, und danach versuchen Sie, in der readLock.lock()
zu parken. In diesem Moment wird der Trägerfaden gefärbt, bis er nicht mehr ausgewählt wird. Das heißt, der Thread verschwindet. In diesem Fall können die Trägerfäden enden, und im Allgemeinen bricht dies die gesamte Idee der Faser.
Der Weg, dies zu lösen, ist bereits bekannt, und darüber wird derzeit diskutiert.
Problem zwei - synchronisierte Blöcke
Das ist viel ernsterer Müll
synchronized (object) {
synchronized (object) {
Im Falle einer Monitorerfassung in der Faser treten auch die Trägerfäden.
Es ist klar, dass Sie in einem völlig neuen Code Monitore in direkte Sperren ändern können, anstatt zu warten + zu benachrichtigen, dass Sie Bedingungsobjekte verwenden können, aber was tun mit Legacy? Das ist ein Problem.
Thread-API? Thread.currentThread ()? Einheimische einfädeln?
Im aktuellen Prototyp haben Thread
und Fiber
eine gemeinsame Superklasse namens Strand
.
Auf diese Weise können Sie die API auf minimalste Weise übertragen.
Was als nächstes zu tun ist - wie immer in diesem Projekt - ist eine Frage.
Was passiert jetzt mit der Thread-API?
- Die erste Verwendung von
Thread.currentThread()
in einer Faser erzeugt eine Art Schatten-Thread, Shadow Thread; - Aus Sicht des Systems handelt es sich um einen "unveröffentlichten" Thread, in dem keine VM-Metainformationen enthalten sind.
- ST versucht alles zu emulieren, was es kann;
- Aber Sie müssen verstehen, dass die alte API viel Müll enthält.
- Insbesondere implementiert Shadow Thread die Thread-API für alles außer
stop
, stop
, resume
und Behandeln nicht erfasster Ausnahmen.
Was tun mit Thread-Einheimischen?
- Jetzt verwandeln sich Thread-Einheimische einfach in Faser-Einheimische.
- Es gibt viele Probleme damit, all dies wird diskutiert;
- Eine Reihe von Verwendungen wird besonders diskutiert.
- Threads wurden in der Vergangenheit sowohl korrekt als auch falsch verwendet (diejenigen, die falsch verwenden, hoffen immer noch auf etwas, und Sie können sie nicht vollständig enttäuschen).
- Dies schafft im Allgemeinen eine ganze Reihe von Anwendungen:
- Übergeordnet: Cache von Verbindungen oder Passwörtern im Container;
- Low-Level: Prozessor in Systembibliotheken.
Wie viel isst das alles?

Thread:
- Stack: 1 MB und 16 KB auf Kernel-Datenstrukturen;
- Pro Thread-Instanz: 2300 Byte, einschließlich VM-Metainformationen.
Faser:
- Fortsetzungsstapel: von Hunderten von Bytes bis Kilobyte;
- Pro Faserinstanz: 200-240 Bytes.
Der Unterschied ist riesig!
Und genau das lässt Millionen von Menschen aufflammen.
Was kann parken
Es ist klar, dass das Magischste das automatische Parken ist, wenn einige Ereignisse eintreten. Was wird aktuell unterstützt?
- Thread.sleep, join;
- java.util.concurrent und LockSupport.lock;
- E / A: Netzwerk auf Sockets (Socket lesen, schreiben, verbinden, akzeptieren), Dateien, Pipes;
- All dies ist unvollendet, aber das Licht im Tunnel ist sichtbar.
Kommunikation zwischen Glasfaser
Eine andere Frage, die sich jeder stellt, ist: Wie kann man Informationen zwischen Fasern wettbewerbsfähig austauschen?
- Der aktuelle Prototyp startet Aufgaben in
Runnable
und kann bei Bedarf in CompletableFuture
konvertiert werden. - java.util.concurrent "funktioniert einfach." Sie können alles auf normale Weise fummeln;
- Möglicherweise gibt es neue APIs für Multithreading, dies ist jedoch nicht korrekt.
- eine Reihe kleiner Fragen wie „Sollten Fasern Werte zurückgeben?“; Alles wird besprochen, sie sind nicht im Prototyp.
Wie werden Fortsetzungen im Prototyp implementiert?
Offensichtliche Anforderungen werden an die Fortsetzung gestellt: Sie müssen so wenig RAM wie möglich verwenden und so schnell wie möglich zwischen ihnen wechseln. Andernfalls wird es nicht funktionieren, sie in Millionenhöhe zu halten. Die Hauptaufgabe hier ist irgendwie, nicht für jeden Park-Uppark eine vollständige Kopie des Stapels zu erstellen. Und es gibt so ein Schema! Versuchen wir dies auf den Bildern zu erklären.
Der coolste Weg wäre natürlich, einfach alle Stapel auf eine Java-Hüfte zu legen und sie direkt zu verwenden. Es ist jedoch nicht klar, wie jetzt codiert werden soll, daher verwendet der Prototyp das Kopieren. Aber mit einem kleinen, aber wichtigen Hack kopieren.
Wir haben zwei Stühle ... ich meine, zwei Stapel. Zwei Java-Arrays in der Hüfte. Eines ist ein Objektarray, in dem Verweise auf Objekte gespeichert werden. Das zweite ist primitiv (zum Beispiel intim), das alles andere erledigt.

Jetzt befinden wir uns in einem Zustand, in dem die Fortsetzung zum ersten Mal durchgeführt werden soll.
run
ruft eine interne Methode namens enter
:

Und dann wird der Benutzercode bis zur ersten Verdrängung ausgeführt.

Zu diesem Zeitpunkt wird ein VM-Aufruf ausgeführt, der das freeze
Anrufen ermöglicht. In diesem Prototyp erfolgt dies direkt physisch - mithilfe der Kopie.

Wir beginnen mit dem sequentiellen Kopieren von Frames vom nativen Stack nach Java Hip.

Es muss überprüft werden, ob die Monitore dort gehalten werden oder der native Code verwendet wird oder etwas anderes, das es uns wirklich nicht erlaubt, weiter zu arbeiten.

Und wenn alles in Ordnung ist, kopieren wir zuerst in ein primitives Array:

Dann isolieren wir die Verweise auf die Objekte und speichern sie im Objektarray:

Eigentlich zwei Tees an alle, die an diesem Ort lesen!
Weiter setzen wir diese Prozedur für alle anderen Elemente des nativen Stapels fort.

Hurra! Wir haben alles in Hüfte ins Nest kopiert. Sie können sicher zum Ort des Anrufs springen, ohne befürchten zu müssen, dass wir etwas verloren haben. Alles ist hip.

Früher oder später ruft der aufrufende Code unsere Fortsetzung erneut auf. Und sie muss von dem Ort aus weitermachen, an dem sie das letzte Mal zurückgelassen wurde. Das ist unsere Aufgabe.

Wenn Sie überprüfen, ob die Fortsetzung ausgeführt wurde, wird "Ja" angezeigt. Sie müssen also VM aufrufen, Speicherplatz auf dem Stapel bereinigen und die interne thaw
VM-Funktion aufrufen. "Tauwetter" wird ins Russische übersetzt als "Tauwetter", "Auftauen", was ziemlich logisch klingt. Es ist notwendig, Frames vom Fortsetzungsstapel in unseren nativen Hauptstapel zu entfernen.
Ich bin mir nicht sicher, ob das Auftauen von Tee ganz klar ist. Schlechte Abstraktion ist wie ein Kätzchen mit einer Tür. Aber das wird für uns reichen.

Wir machen ganz offensichtliche Kopien.
Zuerst mit einem primitiven Array:

Dann vom Link:

Sie müssen das Kopierte ein wenig patchen, um den richtigen Stapel zu erhalten:

Wiederholen Sie Obszönität für alle Frames:

Jetzt können Sie zurückkehren, um yield
und fortzufahren, als wäre nichts passiert.

Das Problem ist, dass eine vollständige Kopie des Stapels überhaupt nicht das ist, was wir gerne hätten. Es ist sehr hemmend. All dies ist die Isolierung von Links, die Überprüfung auf Pinning, es ist nicht schnell. Und vor allem - all dies hängt linear von der Größe des Stapels ab! Mit einem Wort, Hölle. Keine Notwendigkeit, dies zu tun.
Stattdessen haben wir eine andere Idee - faules Kopieren.
Lassen Sie uns zu dem Ort zurückkehren, an dem wir bereits eine eingefrorene Fortsetzung haben.

Wir setzen den Prozess wie bisher fort:

Auf die gleiche Weise wie zuvor bereinigen wir den Platz auf dem nativen Stapel:

Wir kopieren jedoch nicht alles hintereinander, sondern nur einen oder mehrere Frames:

Nun der Hack. Sie müssen die Rücksprungadresse von Methode C
so patchen, dass sie auf eine bestimmte Rückgabesperre verweist:

Jetzt können Sie sicher zurückkehren, um yield
:

Was wiederum zu einem Aufruf des Benutzercodes in der C
Methode führt:

Stellen Sie sich nun vor, C
möchte zu dem Code zurückkehren, der ihn aufgerufen hat. Aber sein Beschwörer ist B
und er ist nicht auf dem Stapel! Wenn er versucht zurückzukehren, geht er daher zur Absenderadresse, und diese Adresse ist jetzt die Rückgabesperre. Und Sie wissen, dies wird wieder einen thaw
auslösen:

Und das thaw
wird den nächsten Frame auf dem Fortsetzungsstapel thaw
, und dies ist B
:

Tatsächlich haben wir es auf Anfrage faul kopiert.
Als nächstes lassen wir B
vom Fortsetzungsstapel fallen und setzen die Barriere erneut (die Barriere muss gesetzt werden, da noch etwas auf dem Fortsetzungsstapel übrig ist). Und so weiter und so fort.

Angenommen, B
kehrt nicht zum aufrufenden Code zurück, sondern ruft zuerst eine andere Methode D
Und diese neue Methode will auch verdrängt werden.

In diesem Fall müssen wir zum freeze
nur die Oberseite des nativen Stapels auf den Fortsetzungsstapel kopieren:

Somit hängt der Arbeitsaufwand nicht linear von der Größe des Stapels ab. Es hängt linear nur von der Anzahl der Frames ab, die wir tatsächlich in der Arbeit verwendet haben.
Was bleibt übrig?
Entwickler berücksichtigen einige Funktionen, sind jedoch nicht auf den Prototyp gekommen.
- Serialisierung und Klonen. Die Möglichkeit, auf einem anderen Computer, zu einem anderen Zeitpunkt usw. fortzufahren.
- JVM TI und Debugging, als wären sie reguläre Threads. Wenn Sie beim Lesen des Sockets blockiert sind, sehen Sie keinen schönen Sprung von der Ausbeute. Im Prototyp wird der Thread einfach blockiert, wie bei jedem anderen normalen Thread.
- Die Schwanzrekursion wurde nicht einmal berührt.
Nächste Schritte:
- Erstellen Sie eine menschliche API.
- Fügen Sie alle fehlenden Funktionen hinzu.
- Leistung verbessern.
Wo zu bekommen
Der Prototyp wird als Brunch im OpenJDK-Repository erstellt. Sie können den Prototyp hier herunterladen, indem Sie zu den Brunchfasern wechseln.
Es wird so gemacht:
$ hg clone http://hg.openjdk.java.net/loom/loom $ cd loom $ hg update -r fibers $ sh configure $ make images
Wie Sie wissen, wird all dies die Montage des ganzen verdammten OpenJDK starten. Daher muss erstens die nächste halbe Stunde Ihres Lebens etwas anderes tun, während all dies geschehen wird.

Zweitens benötigen Sie einen normal konfigurierten Computer mit einer C ++ - Toolchain und GNU-Bibliotheken. Ich weise darauf hin, dass es nicht empfohlen wird, dies unter Windows zu tun. Im Ernst, selbst wenn Sie VirtualBox herunterladen und dort ein neues Ubuntu installieren, verbringen Sie Größenordnungen weniger Zeit als beim Versuch, einen weiteren unmenschlichen Fehler beim Erstellen von Cygwin oder msys64 zu erkennen. Hier kommt msys noch schlimmer ins Spiel als Cygwin.
Obwohl dies natürlich alles eine Lüge ist, habe ich es einfach satt, Ihnen Montageanleitungen zu schreiben .
- , 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
.
, , , .
Fazit
«, ». «», . «Loom» — « ». Project Loom .
, . , «» , , — , , , — .
, , XIX , .

. -, .
, . IDE .
, , , « », «», « » .
? . .
Vielen Dank.