webentwicklung-frage-antwort-db.com.de

nicht blockierend IO vs async IO und Implementierung in Java

Ich versuche, den Unterschied zwischen diesen beiden Konzepten für mich zusammenzufassen (weil ich wirklich verwirrt bin, wenn ich sehe, dass die Leute beide in einem Satz verwenden, wie zum Beispiel "asynchroner Nicht-Blocker-IO"). Ich versuche herauszufinden, was es tut bedeuten).

Nach meinem Verständnis ist Nichtblockieren IO daher vorrangig der Betriebssystemmechanismus zur Verarbeitung von IO, wenn Daten bereit sind. Andernfalls wird nur ein Fehler zurückgegeben/nichts ausgeführt. 

In async IO geben Sie nur einen Rückruf ein und Ihre Anwendung wird benachrichtigt, wenn die Daten verfügbar sind.

Was ist eigentlich "nicht blockierende asynchrone IO"? Und wie sie alle in Java implementiert werden können (Standard-JDK, ohne externe Bibliotheken, ich weiß, dass es Java.nio.channels.{Channels, Selector, SelectorKey} und Java.nio.channels.{AsynchronousSocketChannel} gibt): nicht blockierende E/A, asynchrone E/A und nicht blockierende async IO (falls vorhanden) )

48
akazlou

Was ist eigentlich "nicht-blockierende asynchrone IO"? 

Um das zu beantworten, müssen Sie zuerst verstehen, dass es keine Funktion gibt wie blockiert asynchrone E/A. Das Konzept der Asynchronität besagt, dass es keine Wartezeiten, keine Blockierung oder Verzögerung gibt. Wenn Sie nicht blockierende asynchrone E/A sehen, dient das Bit nicht blockierend nur dazu, das Adjektiv async in diesem Begriff weiter zu qualifizieren. Somit kann nicht blockierende asynchrone E/A ein bisschen Redundanz sein.

Es gibt hauptsächlich zwei Arten von E/A. Synchronous und Asynchronous. Synchronous blockiert den aktuellen Ausführungsthread, bis die Verarbeitung abgeschlossen ist, während Asynchronous den aktuellen Ausführungsthread nicht blockiert, sondern die Steuerung zur weiteren Verarbeitung an den OS-Kernel weitergibt. Der Kernel benachrichtigt den asynchronen Thread, wenn die übergebene Aufgabe abgeschlossen ist.


Asynchrone Kanalgruppen

Das Konzept der asynchronen Kanäle in Java wird von asynchronen Kanalgruppen unterstützt. Eine asynchrone Kanalgruppe fasst grundsätzlich mehrere Kanäle zur Wiederverwendung zusammen. Verbraucher der asynchronen API holen einen Kanal aus der Gruppe ab (die JVM erstellt standardmäßig einen), und der Kanal stellt sich automatisch wieder in die Gruppe ein, nachdem die Lese-/Schreiboperation abgeschlossen ist. Letztendlich werden Async-Kanalgruppen durch Überraschung, Threadpools gesichert. Asynchrone Kanäle sind auch threadsicher. 

Die Größe des Threadpools, der eine asynchrone Kanalgruppe unterstützt, wird durch die folgende JVM-Eigenschaft konfiguriert

Java.nio.channels.DefaultThreadPool.initialSize

bei einem ganzzahligen Wert wird ein Threadpool dieser Größe eingerichtet, um die Kanalgruppe zu sichern. Ansonsten wird die Kanalgruppe transparent für den Entwickler erstellt und verwaltet. 


Und wie alle in Java implementiert werden können

Ich bin froh, dass du gefragt hast. Hier ist ein Beispiel für eine AsynchronousSocketChannel (wird verwendet, um einen nicht blockierenden Client Socket für einen zuhörenden Server zu öffnen.) Dieses Beispiel ist ein Auszug aus Apress Pro Java NIO.2 , kommentiert von mir:

//Create an Asynchronous channel. No connection has actually been established yet
AsynchronousSocketChannel asynchronousSocketChannel = AsynchronousSocketChannel.open(); 

/**Connect to an actual server on the given port and address. 
   The operation returns a type of Future, the basis of the all 
   asynchronous operations in Java. In this case, a Void is 
   returned because nothing is returned after a successful socket connection
  */
Void connect = asynchronousSocketChannel.connect(new InetSocketAddress("127.0.0.1", 5000)).get();


//Allocate data structures to use to communicate over the wire
ByteBuffer helloBuffer = ByteBuffer.wrap("Hello !".getBytes()); 

//Send the message

Future<Integer> successfullyWritten=  asynchronousSocketChannel.write(helloBuffer);

//Do some stuff here. The point here is that asynchronousSocketChannel.write() 
//returns almost immediately, not waiting to actually finish writing 
//the hello to the channel before returning control to the currently executing thread

doSomethingElse();

//now you can come back and check if it was all written (or not)

System.out.println("Bytes written "+successfullyWritten.get());

EDIT: Ich sollte erwähnen, dass die Unterstützung für Async NIO in JDK 1.7 erfolgte

43
kolossus

Ich sehe, dass dies eine alte Frage ist, aber ich denke, dass hier etwas verpasst wurde, worauf @nickdu hinweisen wollte, was aber nicht ganz klar war.

Es gibt vier Arten von IO, die für diese Diskussion relevant sind:

E/A blockieren

Nicht blockierende E/A

Asynchrone E/A

Asynchrone nicht blockierende E/A

Die Verwirrung entsteht meiner Meinung nach durch mehrdeutige Definitionen. Lassen Sie mich versuchen, das zu klären.

Lassen Sie uns zuerst über IO sprechen. Wenn wir langsame IO haben, ist dies am offensichtlichsten, aber IO Operationen können entweder blockierend oder nicht blockierend sein. Dies hat nichts mit Threads zu tun, sondern mit der Schnittstelle zum Betriebssystem. Wenn ich das Betriebssystem nach einer IO -Operation frage, kann ich warten, bis alle Daten bereit sind ( Blockieren ) oder sich zu besorgen, was gerade verfügbar ist und weiterzumachen ( nicht blockierend ). Die Standardeinstellung ist das Blockieren von E/A. Es ist viel einfacher, Code mit dem Blockieren von IO zu schreiben, da der Pfad viel klarer ist. Ihr Code muss jedoch angehalten werden und auf den Abschluss von IO warten. Non-Blocking IO erfordert eine Schnittstelle zu den Bibliotheken IO auf einer niedrigeren Ebene, wobei Select und Read/Write anstelle der Bibliotheken auf höherer Ebene verwendet werden, die praktische Operationen bereitstellen. Non-Blocking IO impliziert auch, dass Sie etwas haben, an dem Sie arbeiten müssen, während das Betriebssystem die E/A ausführt. Dies können mehrere IO Vorgänge oder Berechnungen für IO sein, die abgeschlossen wurden.

E/A blockieren - Die Anwendung wartet darauf, dass das Betriebssystem alle Bytes erfasst, um den Vorgang abzuschließen oder das Ende zu erreichen, bevor sie fortfährt. Dies ist die Standardeinstellung. Um für sehr technisch klar zu sein, installiert der Systemaufruf, der IO initiiert, einen Signalhandler, der auf eine Prozessorunterbrechung wartet, die auftritt, wenn die IO -Operation Fortschritte macht. Dann beginnt der Systemaufruf mit einem Ruhezustand, der den Betrieb des aktuellen Prozesses für einen bestimmten Zeitraum oder bis zur Unterbrechung des Prozesses unterbricht.

Non-Blocking IO - Die Anwendung teilt dem Betriebssystem mit, dass nur die aktuell verfügbaren Bytes benötigt werden, und fährt fort, während das Betriebssystem gleichzeitig weitere Bytes sammelt. Der Code verwendet select, um zu bestimmen, für welche IO Operationen Bytes verfügbar sind. In diesem Fall installiert der Systemaufruf erneut einen Signal-Handler. Statt jedoch zu schlafen, wird der Signal-Handler dem Datei-Handler zugeordnet und sofort zurückgegeben. Der Prozess wird dafür verantwortlich sein, das Datei-Handle regelmäßig auf das gesetzte Interrupt-Flag zu überprüfen. Dies erfolgt normalerweise mit einem ausgewählten Anruf.

Jetzt beginnt bei Asynchronous die Verwirrung. Das allgemeine Konzept der Asynchronität impliziert lediglich, dass der Prozess fortgesetzt wird, während die Hintergrundoperation ausgeführt wird. Der Mechanismus, durch den dies auftritt, ist nicht spezifisch. Der Begriff ist mehrdeutig, da sowohl nicht blockierendes IO als auch Thread-blockierendes IO als asynchron betrachtet werden können. Beide ermöglichen gleichzeitige Vorgänge, die Ressourcenanforderungen sind jedoch unterschiedlich und der Code unterscheidet sich erheblich. Da Sie die Frage "Was ist nicht blockierendes asynchrones E/A" gestellt haben, verwende ich eine strengere Definition für asynchrones, ein Thread-System, das IO ausführt, das möglicherweise nicht blockiert oder nicht blockiert.

Die allgemeine Definition

Asynchrone E/A - Programmatische IO, mit der mehrere IO Vorgänge gleichzeitig ausgeführt werden können. IO Vorgänge werden gleichzeitig ausgeführt, sodass der Code nicht auf Daten wartet, die nicht bereit sind.

Die strengere Definition

Asynchrones E/A - Programmatisches IO, das Threading oder Mehrfachverarbeitung verwendet, um gleichzeitige IO Vorgänge zu ermöglichen.

Mit diesen klareren Definitionen haben wir die folgenden vier Typen von IO Paradigmen.

Blockieren von E/A - Standard-Einzel-Thread-IO, in dem die Anwendung vor dem Verschieben darauf wartet, dass alle IO Vorgänge abgeschlossen sind auf. Einfach zu codieren, keine Parallelität und daher langsam für Anwendungen, die mehrere IO Vorgänge erfordern. Der Prozess oder Thread wird in den Ruhezustand versetzt, während auf das Auftreten des Interrupts IO gewartet wird.

Asynchrone E/A - Threaded IO, in dem die Anwendung Ausführungs-Threads verwendet, um Blocking IO -Operationen gleichzeitig auszuführen. Erfordert thread-sicheren Code, ist jedoch im Allgemeinen leichter zu lesen und zu schreiben als die Alternative. Gewinnt den Overhead mehrerer Threads, verfügt jedoch über eindeutige Ausführungspfade. Möglicherweise müssen synchronisierte Methoden und Container verwendet werden.

Non-Blocking IO - Single-Threaded IO, in dem die Anwendung select verwendet, um die IO Vorgänge zu bestimmen Bereit zum Fortfahren, wodurch die Ausführung von anderem Code oder anderen IO Vorgängen ermöglicht wird, während das Betriebssystem gleichzeitige E/A-Vorgänge verarbeitet. Der Prozess schläft nicht, während er auf den Interrupt IO wartet, sondern übernimmt die Verantwortung, auf das Flag IO im Dateihandle zu prüfen. Viel komplizierterer Code, da das IO -Flag mit select markiert werden muss, jedoch kein threadsicherer Code oder synchronisierte Methoden und Container erforderlich sind. Geringer Ausführungsaufwand auf Kosten der Codekomplexität. Ausführungspfade sind verschlungen.

Asynchrone nicht blockierende E/A - Ein hybrider Ansatz für IO mit dem Ziel, die Komplexität durch die Verwendung von Threads zu verringern und gleichzeitig die Skalierbarkeit durch die Verwendung von nicht blockierenden E/A zu gewährleisten. Blockieren von IO Operationen, wo dies möglich ist. Dies wäre der komplexeste Typ von IO, der synchronisierte Methoden und Container sowie verschlungene Ausführungspfade erfordert. Dies ist nicht der Typ von IO, den man leichtfertig codieren sollte, und wird am häufigsten nur verwendet, wenn eine Bibliothek verwendet wird, die die Komplexität maskiert, wie etwa Futures und Promises.

86
AaronM

Ich würde sagen, es gibt drei Arten von IO:

synchrone Blockierung
synchron nicht blockierend
asynchron

Sowohl synchrones nicht blockieren als auch asynchron werden als nicht blockierend betrachtet, da der aufrufende Thread nicht auf die Beendigung von IO wartet. Nicht-blockierende asynchrone IOs sind zwar redundant, aber nicht identisch. Wenn ich eine Datei öffne, kann ich sie im nicht blockierenden Modus öffnen. Was bedeutet das? Das heißt, wenn ich ein read () ausstelle, wird es nicht blockiert. Es gibt entweder die verfügbaren Bytes zurück oder zeigt an, dass keine Bytes verfügbar sind. Wenn ich nicht-blockierendes io nicht aktiviere, würde das read () blockieren, bis Daten verfügbar waren. Ich möchte möglicherweise Nicht-Blockierungs-IO aktivieren, wenn ein Thread mehrere IO-Anforderungen verarbeiten soll. Zum Beispiel könnte ich select () verwenden, um herauszufinden, welche Dateideskriptoren oder möglicherweise Sockets Daten enthalten, die gelesen werden können. Ich mache dann synchrones Lesen dieser Dateideskriptoren. Keines dieser Lesevorgänge sollte blockieren, da ich bereits weiß, dass Daten verfügbar sind. Außerdem habe ich die Dateideskriptoren im nicht blockierenden Modus geöffnet.

Asynchrone IO-Anweisungen geben Sie eine IO-Anforderung aus. Diese Anforderung befindet sich in einer Warteschlange und blockiert daher den ausstellenden Thread nicht. Sie werden benachrichtigt, wenn die Anforderung fehlgeschlagen ist oder erfolgreich abgeschlossen wurde.

4
nickdu

Nicht blockierende E/A ist der Zeitpunkt, an dem der Aufruf zur Ausführung von IO) sofort zurückkehrt und Ihren Thread nicht blockiert.

Die einzige Möglichkeit, festzustellen, ob IO erledigt ist, besteht darin, seinen Status oder Block abzufragen. Stellen Sie sich das als Future vor. Sie starten ein IO Operation, und es gibt Ihnen ein Future zurück. Sie können isDone() aufrufen, um zu prüfen, ob es fertig ist, wenn es fertig ist, machen Sie, was Sie wollen, andernfalls machen Sie weiter andere Dinge, bis Sie das nächste Mal prüfen möchten, ob sie erledigt sind. Oder, wenn Sie nichts zu tun haben, können Sie get aufrufen, das blockiert, bis es erledigt ist.

Async IO ist, wenn der Aufruf zur Ausführung IO benachrichtigt Sie, dass es durch ein Ereignis erfolgt, nicht durch seinen Rückgabewert .

Dies kann blockierend oder nicht blockierend sein.

Blockieren von asynchronem E/A

Mit dem Blockieren von async IO ist gemeint, dass der auszuführende Aufruf IO= ein normaler Blockierungsaufruf ist, aber das, was Sie aufgerufen haben, hat diesen Aufruf in einen Thread eingeschlossen, der wird blockieren, bis IO erledigt ist, und dann die Behandlung des Ergebnisses von IO an Ihren Rückruf delegieren. Das heißt, es gibt noch einen Thread weiter unten der Stack, der auf dem IO blockiert ist, aber Ihr Thread nicht.

Nicht blockierendes asynchrones E/A

Dies ist tatsächlich die üblichere und bedeutet, dass der nicht blockierende Wert IO nicht wie bei nicht blockierenden Standard-E/A nach seinem Status abgefragt werden muss, sondern stattdessen Ihren Rückruf aufruft Im Gegensatz zum Blockieren von asynchronen E/A-Vorgängen werden in diesem keine Threads im gesamten Stapel blockiert. Dies ist schneller und beansprucht weniger Ressourcen, da das asynchrone Verhalten verwaltet wird, ohne dass Threads blockiert werden.

Sie können sich das als CompletableFuture vorstellen. Voraussetzung ist, dass Ihr Programm über eine Art asynchrones Ereignis-Framework verfügt, das multithreaded sein kann oder nicht. Es ist also möglich, dass der Rückruf in einem anderen Thread ausgeführt wird oder dass die Ausführung in einem vorhandenen Thread geplant ist, sobald die aktuelle Aufgabe erledigt ist.

Ich erkläre die Unterscheidung genauer hier.

3
Didier A.