Heim >Java >javaLernprogramm >Detaillierte grafische und textliche Erläuterung der IO-Wiederverwendung in Java

Detaillierte grafische und textliche Erläuterung der IO-Wiederverwendung in Java

黄舟
黄舟Original
2017-05-28 09:23:001496Durchsuche

Dieser Artikel stellt hauptsächlich das relevante Wissen über die Wiederverwendung von Java IO vor. Er ist sehr gut und hat Referenzwert.

Für die gleichzeitige Verarbeitung des Servers Was wir brauchen, ist: Jede Millisekunde kann der Server die innerhalb dieser Millisekunde empfangenen Nachrichten auf Hunderten verschiedener TCP-Verbindungen umgehend verarbeiten. Gleichzeitig können Hunderttausende Nachrichten auf dem Server vorhanden sein, die in der letzten Sekunde nicht empfangen wurden Einige Sekunden lang ist eine relativ inaktive Verbindung zum Senden und Empfangen von Nachrichten erforderlich. Die gleichzeitige Verarbeitung mehrerer Verbindungen, bei denen Ereignisse auftreten, wird als Parallelität bezeichnet. Die gleichzeitige Verarbeitung von Zehntausenden oder Hunderttausenden Verbindungen ist eine hohe Parallelität. Das Ziel der gleichzeitigen Programmierung des Servers besteht darin, eine unendliche Anzahl gleichzeitiger Verbindungen zu verarbeiten und gleichzeitig eine effiziente Nutzung von Ressourcen wie der CPU aufrechtzuerhalten, bis die physischen Ressourcen zum ersten Mal erschöpft sind.

Es gibt viele Implementierungen von Modellen für die gleichzeitige Programmierung, die einfachste ist mit „Threads“ gebündelt und ein Thread verwaltet den gesamten Lebenszyklus einer Verbindung. Vorteile: Dieses Modell ist einfach genug, es kann komplexe Geschäftsszenarien implementieren und gleichzeitig kann die Anzahl der Threads viel größer sein als die Anzahl der CPUs. Die Anzahl der Threads kann jedoch nicht unendlich erhöht werden. Da der Zeitpunkt der Ausführung eines Threads durch den Planungsalgorithmus des Betriebssystemkerns bestimmt wird, berücksichtigt der Planungsalgorithmus nicht, dass ein bestimmter Thread möglicherweise nur eine Verbindung bedient. Er übernimmt ein einheitliches Gameplay: Führen Sie ihn aus, wenn die Zeitscheibe abgelaufen ist Dieser Thread Nach der Ausführung müssen Sie weiter schlafen. Dieses Hin- und Herwechseln zwischen Aufwachen und Ruhen von Threads ist günstig, wenn die Häufigkeit gering ist. Wenn jedoch die Gesamtzahl der Threads im Betriebssystem groß ist, ist es teuer (verstärkt), da sich dieser technische Planungsverlust auf die Threads auswirkt Der Zeitpunkt, zu dem der Geschäftscode ausgeführt wird. Beispielsweise ist die Ausführungseffizienz der meisten Threads mit inaktiven Verbindungen zu diesem Zeitpunkt zu niedrig. Sie wachen auf und konkurrieren um CPU-Ressourcen bedeutet, dass die Anzahl der privaten Unternehmensthreads, die aktive Verbindungen verarbeiten, verringert wird und die Möglichkeit, die CPU zu erhalten, verringert wird. Die CPU ist der Kern der Wettbewerbsfähigkeit, und ihre Ineffizienz wirkt sich auf den gesamten BIP-Durchsatz aus. Unser Ziel ist es, Hunderttausende von Verbindungen gleichzeitig zu verarbeiten. Wenn Tausende von Threads auftreten, kann die Ausführungseffizienz des Systems einer hohen Parallelität nicht mehr gerecht werden.

Für die Programmierung mit hoher Parallelität gibt es derzeit nur ein Modell, das im Wesentlichen die einzig wirksame Methode ist. Die Nachrichtenverarbeitung auf der Verbindung kann in zwei Phasen unterteilt werden: Warten auf die Bereitstellung der Nachricht und Nachrichtenverarbeitung. Bei Verwendung des standardmäßigen blockierenden Sockets (z. B. ein Thread, der oben zum Verarbeiten einer Verbindung gebündelt ist) werden diese beiden Phasen häufig zu einer kombiniert, sodass der Thread, der den Socket-Code betreibt, warten muss, bis die Nachricht bereit ist Dies führt dazu, dass der Thread bei hoher Parallelität häufig in den Ruhezustand wechselt und wieder aufwacht, was sich auf die Effizienz der CPU-Nutzung auswirkt.

Die Programmiermethode mit hoher Parallelität besteht natürlich darin, die beiden Phasen zu trennen. Das heißt, der Codeabschnitt, der darauf wartet, dass die Nachricht bereit ist, wird vom Codeabschnitt getrennt, der die Nachricht verarbeitet. Dies erfordert natürlich auch, dass der Socket nicht blockierend ist. Andernfalls kann das Codesegment, das die Nachricht verarbeitet, leicht dazu führen, dass der Thread in die Ruhezustandswartephase eintritt, wenn die Bedingungen nicht erfüllt sind. Die Frage ist also: Wie erreicht man diese Phase des Wartens, bis die Nachricht fertig ist? Schließlich wartet es noch, was bedeutet, dass der Thread noch schlafen muss! Die Lösung besteht darin, aktiv abzufragen oder einen Thread auf alle Verbindungen warten zu lassen! Dies ist IO-Multiplexing. Beim Multiplexen geht es darum, auf die Bereitstellung von Nachrichten zu warten, aber es können mehrere Verbindungen gleichzeitig verarbeitet werden! Es kann auch „warten“, sodass der Thread möglicherweise auch in den Ruhezustand versetzt wird. Dies spielt jedoch keine Rolle, da es eins-zu-viele ist und alle Verbindungen überwachen kann. Wenn unser Thread zur Ausführung aktiviert wird, müssen auf diese Weise einige Verbindungen vorhanden sein, die für die Ausführung durch unseren Code bereitstehen, was effizient ist! Es gibt nicht so viele Threads, die um die Verarbeitung der Phase „Warten auf die Fertigstellung der Nachricht“ konkurrieren, und die ganze Welt ist endlich klar!
Unter Linux wurden sie hauptsächlich ausgewählt und abgefragt. Ihre Verwendungsmethoden scheinen sehr unterschiedlich zu sein das gleiche.

Auch die Effizienz ist unterschiedlich, weshalb epoll select komplett ersetzt hat.

Lassen Sie uns kurz darüber sprechen, warum Epoll Select ersetzt.

Wie bereits erwähnt, besteht die Kernlösung für hohe Parallelität darin, dass ein Thread das „Warten auf die Bereitstellung von Nachrichten“ für alle Verbindungen verwaltet. In diesem Punkt sind Epoll und Select unumstritten. Aber wählen Sie eine Sache falsch aus. Wie wir im Eröffnungskapitel von gesagt haben, gibt es möglicherweise nur Hunderte von aktiven Verbindungen pro Millisekunde, während die restlichen Hunderttausende von Verbindungen vorhanden sind ist während dieser Millisekunde inaktiv. Die Methode zur Verwendung von „select“ lautet wie folgt:
Zurückgegebene aktive Verbindungen ==select (alle zu überwachenden Verbindungen)

Wann wird die select-Methode aufgerufen? Sie sollten dies aufrufen, wenn Sie herausfinden möchten, welche aktiven Verbindungen Pakete empfangen haben. Daher wird der Aufruf von select häufig aufgerufen, wenn die Parallelität hoch ist. Auf diese Weise muss geprüft werden, ob diese häufig genannte Methode effizient ist, da ihr geringfügiger Effizienzverlust durch das Wort „häufig“ verstärkt wird. Gibt es einen Effizienzverlust? Offensichtlich müssen Hunderttausende Verbindungen überwacht werden und es werden nur Hunderte aktive Verbindungen zurückgegeben, was an sich ineffizient ist. Nach der Erweiterung werden Sie feststellen, dass select nicht in der Lage ist, Zehntausende gleichzeitiger Verbindungen zu verarbeiten.
Schauen Sie sich einige Bilder an. Wenn die Anzahl gleichzeitiger Verbindungen weniger als 1.000 beträgt, ist die Anzahl ausgewählter Ausführungen nicht häufig und es scheint keinen großen Unterschied zu Epoll zu geben:

Sobald jedoch die Anzahl der Parallelitäten zunimmt, werden die Mängel von Select durch die „häufige Ausführung“ unendlich vergrößert. Je mehr Parallelität vorhanden ist, desto offensichtlicher ist:

Lassen Sie uns darüber sprechen, wie Epoll das Problem löst. Es verwendet geschickt drei Methoden, um das zu erreichen, was die Auswahlmethode bewirkt:

Neuer Epoll-Deskriptor==epoll_create()

epoll_ctrl(Epoll-Deskriptor, hinzufügen oder Löschen alle zu überwachenden Verbindungen)

Die zurückgegebene aktive Verbindung==epoll_wait (epoll-Deskriptor)

Der Hauptvorteil dieser Vorgehensweise besteht darin, zwischen häufigen Aufrufen und selten aufgerufenen Vorgängen zu unterscheiden. Beispielsweise wird epoll_ctrl seltener aufgerufen, während epoll_wait sehr häufig aufgerufen wird. Zu diesem Zeitpunkt hat epoll_wait fast keine Eingabeparameter, was viel effizienter ist als select. Darüber hinaus erhöht es die Anzahl der Eingabeparameter nicht, wenn gleichzeitige Verbindungen zunehmen, was zu einer Verringerung der Kernel-Ausführungseffizienz führt.

Wie wird Epoll implementiert? Tatsächlich ist es sehr einfach, aus diesen drei Methoden zu ersehen, dass es intelligenter ist als „select“, da es nicht erforderlich ist, jedes Mal alle zu überwachenden Verbindungen zu übergeben, wenn epoll_wait häufig aufruft, „welche Verbindungen sich bereits in der Nachrichtenvorbereitung befinden“. Bühne". von. Dies bedeutet, dass im Kernelmodus eine Datenstruktur verwaltet wird, um alle zu überwachenden Verbindungen zu speichern. Diese Datenstruktur ist ein rot-schwarzer Baum, und das Hinzufügen und Reduzieren seiner Knoten wird über epoll_ctrl abgeschlossen. Es ist ganz einfach:

Der rot-schwarze Baum unten links im Bild besteht aus allen zu überwachenden Verbindungen. Die verknüpfte Liste oben links zeigt alle derzeit aktiven Verbindungen. Wenn epoll_wait ausgeführt wird, überprüft es daher nur die verknüpfte Liste oben links und gibt die Verbindung in der verknüpften Liste oben links an den Benutzer zurück. Kann die Ausführungseffizienz von epoll_wait auf diese Weise niedrig sein?

Schließlich werfen wir einen Blick auf die beiden von Epoll bereitgestellten Spielmethoden ET und LT, nämlich den übersetzten Edge-Trigger und den horizontalen Trigger. Tatsächlich sind diese beiden chinesischen Namen einigermaßen passend. Diese beiden Verwendungsmethoden zielen immer noch auf Effizienzprobleme ab, dienen jedoch lediglich dazu, die von epoll_wait zurückgegebene Verbindung genauer zu machen.

Zum Beispiel müssen wir überwachen, ob der Schreibpuffer einer Verbindung frei ist. Wenn er „beschreibbar“ ist, können wir den Antwortaufruf write aus dem Benutzermodus an den Client senden. Wenn die Verbindung jedoch beschreibbar ist, befindet sich unser „Antwort“-Inhalt möglicherweise noch auf der Festplatte. Was ist, wenn der Lesevorgang auf der Festplatte zu diesem Zeitpunkt noch nicht abgeschlossen ist? Der Thread darf nicht blockiert sein, damit die Antwort nicht gesendet wird. Beim nächsten epoll_wait wird die Verbindung jedoch möglicherweise an Sie zurückgegeben, und Sie müssen prüfen, ob Sie sie verarbeiten möchten. Möglicherweise verfügt unser Programm über ein weiteres Modul, das speziell Festplatten-E/A verarbeitet und eine Antwort sendet, wenn die Festplatten-E/A abgeschlossen ist. Erfüllt epoll_wait also jedes Mal, wenn diese „beschreibbare“ Verbindung zurückgegeben wird, die nicht sofort verarbeitet werden kann, die Erwartungen des Benutzers?

So entstanden die ET- und LT-Modi. LT bedeutet, dass jede Verbindung, die den erwarteten Status erfüllt, in epoll_wait zurückgegeben werden muss, damit alle gleich behandelt werden und sich auf einer horizontalen Linie befindet. Dies ist bei ET nicht der Fall, das präzisere Rückverbindungen bevorzugt. Wenn im obigen Beispiel die Verbindung zum ersten Mal beschreibbar wird und das Programm keine Daten in die Verbindung schreibt, gibt epoll_wait die Verbindung beim nächsten Mal nicht zurück. ET wird als Edge-Trigger bezeichnet, was bedeutet, dass epoll_wait nur dann ausgelöst wird, wenn die Verbindung von einem Zustand in einen anderen wechselt. Es ist ersichtlich, dass die Programmierung von ET viel komplizierter ist. Zumindest muss die Anwendung darauf achten, dass die von epoll_wait zurückgegebene Verbindung nicht angezeigt wird: Wenn sie beschreibbar ist, werden die Daten nicht geschrieben, sondern sie erwartet die nächste „beschreibbare“ Verbindung. Wenn es lesbar ist, werden die Daten nicht gelesen, aber beim nächsten Mal wird erwartet, dass sie „lesbar“ sind.

Natürlich wird es in allgemeinen Anwendungsszenarien keinen großen Leistungsunterschied geben. Der mögliche Vorteil von ET besteht darin, dass die Anzahl der Aufrufe von epoll_wait reduziert wird und in einigen Szenarien die Verbindung nicht aktiviert wird ist nicht notwendig (Dieses Aufwachen bezieht sich auf die Rückkehr von epoll_wait). Aber wenn es wie das oben erwähnte Beispiel ist, liegt es manchmal nicht nur an einem Netzwerkproblem, sondern hängt mit dem Anwendungsszenario zusammen. Natürlich sind die meisten Open-Source--Frameworks auf Basis von ET geschrieben. Was das Framework betrifft, verfolgt es rein technische Probleme und strebt natürlich nach Perfektion

Das obige ist der detaillierte Inhalt vonDetaillierte grafische und textliche Erläuterung der IO-Wiederverwendung in Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn