Was ist der Unterschied zwischen asynchronen und nicht blockierenden Anrufen? Auch zwischen blockierenden und synchronen Anrufen (mit Beispielen bitte)?
Was ist der Unterschied zwischen asynchronen und nicht blockierenden Anrufen? Auch zwischen blockierenden und synchronen Anrufen (mit Beispielen bitte)?
Antworten:
In vielen Fällen sind sie unterschiedliche Namen für dieselbe Sache, aber in einigen Kontexten sind sie sehr unterschiedlich. Es kommt also darauf an. Die Terminologie wird in der gesamten Softwareindustrie nicht vollständig konsistent angewendet.
In der klassischen Sockets-API wird beispielsweise ein nicht blockierender Socket einfach sofort mit einer speziellen Fehlermeldung "würde blockieren" zurückgegeben, während ein blockierender Socket blockiert hätte. Sie müssen eine separate Funktion verwenden, z. B. select
oder poll
um herauszufinden, wann ein guter Zeitpunkt für einen erneuten Versuch ist.
Asynchrone Sockets (wie von Windows-Sockets unterstützt) oder das in .NET verwendete asynchrone E / A-Muster sind jedoch praktischer. Sie rufen eine Methode auf, um eine Operation zu starten, und das Framework ruft Sie zurück, wenn dies abgeschlossen ist. Auch hier gibt es grundlegende Unterschiede. Asynchrone Win32-Sockets "marshallen" ihre Ergebnisse auf einem bestimmten GUI-Thread, indem sie Windows-Nachrichten übergeben, während asynchrone .NET-E / A-Vorgänge mit freiem Thread ausgeführt werden (Sie wissen nicht, auf welchem Thread Ihr Rückruf aufgerufen wird).
Sie bedeuten also nicht immer dasselbe. Um das Socket-Beispiel zu destillieren, könnten wir sagen:
synchron / asynchron soll die Beziehung zwischen zwei Modulen beschreiben.
Blockieren / Nichtblockieren beschreibt die Situation eines Moduls.
Ein Beispiel:
Modul X: "I".
Modul Y: "Buchhandlung".
X fragt Y: Haben Sie ein Buch mit dem Namen "c ++ primer"?
1) Blockieren: Bevor Y auf X antwortet, wartet X dort auf die Antwort. Jetzt blockiert X (ein Modul). X und Y sind zwei Threads oder zwei Prozesse oder ein Thread oder ein Prozess? Wir wissen es nicht.
2) nicht blockierend: Bevor Y auf X antwortet, verlässt X es einfach und macht andere Dinge. X kann alle zwei Minuten zurückkommen, um zu überprüfen, ob Y seine Arbeit beendet hat? Oder kommt X nicht zurück, bis Y ihn anruft? Wir wissen es nicht. Wir wissen nur, dass X andere Dinge tun kann, bevor Y seine Arbeit beendet. Hier ist X (ein Modul) nicht blockierend. X und Y sind zwei Threads oder zwei Prozesse oder ein Prozess? Wir wissen es nicht. ABER wir sind sicher, dass X und Y nicht ein Thread sein können.
3) synchron: Bevor Y auf X antwortet, wartet X dort auf die Antwort. Dies bedeutet, dass X nicht fortfahren kann, bis Y seinen Job beendet hat. Jetzt sagen wir: X und Y (zwei Module) sind synchron. X und Y sind zwei Threads oder zwei Prozesse oder ein Thread oder ein Prozess? Wir wissen es nicht.
4) asynchron: Bevor Y auf X antwortet, verlässt X dort und X kann andere Aufgaben erledigen. X wird nicht zurückkommen, bis Y ihn anruft. Jetzt sagen wir: X und Y (zwei Module) sind asynchron. X und Y sind zwei Threads oder zwei Prozesse oder ein Prozess? Wir wissen es nicht. ABER wir sind sicher, dass X und Y nicht ein Thread sein können.
Bitte beachten Sie die beiden obigen fett gedruckten Sätze. Warum enthält der fette Satz in 2) zwei Fälle, während der fette Satz in 4) nur einen Fall enthält? Dies ist ein Schlüssel zum Unterschied zwischen nicht blockierend und asynchron.
Hier ist ein typisches Beispiel für nicht blockierend und synchron:
// thread X
while (true)
{
msg = recv(Y, NON_BLOCKING_FLAG);
if (msg is not empty)
{
break;
}
sleep(2000); // 2 sec
}
// thread Y
// prepare the book for X
send(X, book);
Sie können sehen, dass dieses Design nicht blockiert (Sie können sagen, dass diese Schleife die meiste Zeit etwas Unsinniges tut, aber in den Augen der CPU läuft X, was bedeutet, dass X nicht blockiert), während X und Y synchron sind, weil X dies kann Machen Sie keine weiteren Dinge (X kann nicht aus der Schleife herausspringen), bis das Buch von Y stammt.
Normalerweise ist das Blockieren von X in diesem Fall viel besser, da das Nicht-Blockieren viel Ressourcen für eine dumme Schleife verbraucht. Dieses Beispiel hilft Ihnen jedoch dabei, die Tatsache zu verstehen: Nicht blockieren bedeutet nicht asynchron.
Die vier Wörter machen uns leicht verwirrt. Wir sollten uns daran erinnern, dass die vier Wörter für die Gestaltung der Architektur dienen. Nur wenn Sie lernen, wie man eine gute Architektur entwirft, können Sie sie unterscheiden.
Zum Beispiel können wir eine solche Art von Architektur entwerfen:
// Module X = Module X1 + Module X2
// Module X1
while (true)
{
msg = recv(many_other_modules, NON_BLOCKING_FLAG);
if (msg is not null)
{
if (msg == "done")
{
break;
}
// create a thread to process msg
}
sleep(2000); // 2 sec
}
// Module X2
broadcast("I got the book from Y");
// Module Y
// prepare the book for X
send(X, book);
Im Beispiel hier können wir das sagen
Bei Bedarf können Sie auch die in X1 erstellten Threads mit den vier Wörtern beschreiben.
Die wichtigeren Dinge sind: Wann verwenden wir synchron statt asynchron? Wann verwenden wir das Blockieren anstelle des Nicht-Blockierens?
Warum blockiert Nginx nicht? Warum blockiert Apache?
Um eine gute Wahl zu treffen, müssen Sie Ihren Bedarf analysieren und die Leistung verschiedener Architekturen testen. Es gibt keine solche Architektur, die für verschiedene Anforderungen geeignet ist.
Wenn Sie diese Frage in Java 7 in den Kontext von NIO und NIO.2 stellen, ist async IO einen Schritt weiter fortgeschritten als das Nicht-Blockieren. Bei nicht blockierenden Java NIO-Aufrufen würden alle Kanäle (SocketChannel, ServerSocketChannel, FileChannel usw.) durch Aufrufen als solche festgelegt AbstractSelectableChannel.configureBlocking(false)
. Nach der Rückkehr dieser E / A-Aufrufe müssen Sie jedoch wahrscheinlich noch die Überprüfungen steuern, z. B. ob und wann erneut gelesen / geschrieben werden muss usw.
Zum Beispiel:
while (!isDataEnough()) {
socketchannel.read(inputBuffer);
// do something else and then read again
}
Mit der asynchronen API in Java 7 können diese Steuerelemente auf vielseitigere Weise erstellt werden. Eine der beiden Möglichkeiten ist die Verwendung CompletionHandler
. Beachten Sie, dass beide read
Anrufe nicht blockieren.
asyncsocket.read(inputBuffer, 60, TimeUnit.SECONDS /* 60 secs for timeout */,
new CompletionHandler<Integer, Object>() {
public void completed(Integer result, Object attachment) {...}
public void failed(Throwable e, Object attachment) {...}
}
}
FileChannel
ist nicht auswählbar und kann nicht als nicht blockierend konfiguriert werden.
Wie Sie wahrscheinlich an der Vielzahl unterschiedlicher (und sich oft gegenseitig ausschließender) Antworten erkennen können, hängt es davon ab, wen Sie fragen. In einigen Bereichen sind die Begriffe synonym. Oder sie beziehen sich jeweils auf zwei ähnliche Konzepte:
In beiden Fällen soll zugelassen werden, dass das Programm nicht blockiert wird, bis ein langsamer Prozess abgeschlossen ist. Wie das Programm voraussichtlich reagieren wird, ist der einzige wirkliche Unterschied. Welcher Begriff sich auf welchen bezieht, ändert sich auch von Programmierer zu Programmierer, von Sprache zu Sprache oder von Plattform zu Plattform. Oder die Begriffe können sich auf völlig unterschiedliche Konzepte beziehen (z. B. die Verwendung von synchron / asynchron in Bezug auf die Thread-Programmierung).
Entschuldigung, aber ich glaube nicht, dass es eine einzige richtige Antwort gibt, die global wahr ist.
Ein nicht blockierender Aufruf wird sofort mit allen verfügbaren Daten zurückgegeben: der vollen Anzahl angeforderter Bytes, weniger oder gar keiner.
Ein asynchroner Anruf fordert eine Übertragung an, die vollständig (vollständig) ausgeführt wird, aber zu einem späteren Zeitpunkt abgeschlossen wird.
Nicht blockierend: Diese Funktion wartet nicht auf dem Stapel.
Asynchron: Die Arbeit kann im Namen des Funktionsaufrufs fortgesetzt werden, nachdem dieser Aufruf den Stapel verlassen hat
Synchron wird als gleichzeitig ablaufend definiert.
Asynchron ist definiert als nicht gleichzeitig.
Dies ist es, was die erste Verwirrung verursacht. Synchron ist eigentlich das, was als parallel bekannt ist. Während Asynchron sequentiell ist, tun Sie dies und dann das.
Das ganze Problem besteht nun darin, ein asynchrones Verhalten zu modellieren, da Sie eine Operation haben, die die Antwort einer anderen benötigt, bevor sie beginnen kann. Es handelt sich also um ein Koordinationsproblem. Woher wissen Sie, dass Sie diese Operation jetzt starten können?
Die einfachste Lösung ist das Blockieren.
Beim Blockieren warten Sie einfach, bis das andere erledigt ist, und erhalten eine Antwort, bevor Sie mit dem Vorgang fortfahren, der dies erfordert.
Wenn Sie also Butter auf Toast legen müssen und daher zuerst die gezüchteten Toastbrotstücke benötigen. Die Art und Weise, wie Sie sie koordinieren würden, besteht darin, dass Sie zuerst das gezüchtete Toaststück anstoßen, dann endlos auf den Toaster starren, bis der Toast platzt, und dann Butter darauf legen.
Es ist die einfachste Lösung und funktioniert sehr gut. Es gibt keinen wirklichen Grund, es nicht zu verwenden, es sei denn, Sie haben zufällig auch andere Dinge zu tun, die keine Koordination mit den Operationen erfordern. Zum Beispiel Geschirr spülen. Warum warten Sie im Leerlauf, während Sie den Toaster ständig anstarren, bis der Toast platzt, wenn Sie wissen, dass es einige Zeit dauern wird und Sie ein ganzes Gericht waschen können, während es fertig ist?
Hier kommen zwei weitere Lösungen ins Spiel, die als nicht blockierend bzw. asynchron bezeichnet werden.
Nicht blockierend ist, wenn Sie sich dafür entscheiden, andere Dinge zu tun, die nichts miteinander zu tun haben, während Sie auf den Vorgang warten. Überprüfen Sie die Verfügbarkeit der Antwort nach Belieben.
Also anstatt auf den Toaster zu schauen, damit er platzt. Du gehst und waschst ein ganzes Gericht. Und dann guckst du auf den Toaster, um zu sehen, ob die Toasts geplatzt sind. Wenn dies nicht der Fall ist, spülen Sie ein anderes Gericht und überprüfen den Toaster zwischen den einzelnen Gerichten. Wenn Sie sehen, dass die Toasts geplatzt sind, hören Sie auf, das Geschirr zu spülen. Stattdessen nehmen Sie den Toast und legen Butter darauf.
Es kann jedoch ärgerlich sein, ständig nach Toasts suchen zu müssen. Stellen Sie sich vor, der Toaster befindet sich in einem anderen Raum. Zwischen den Gerichten verschwenden Sie Ihre Zeit damit, in den anderen Raum zu gehen, um nach dem Toast zu sehen.
Hier kommt asynchron.
Asynchron ist, wenn Sie andere Dinge ausführen, die nichts miteinander zu tun haben, während Sie auf die Ausführung des Vorgangs warten. Anstatt es zu überprüfen, delegieren Sie die Überprüfungsarbeit an etwas anderes, das die Operation selbst oder ein Beobachter sein kann, und Sie werden von diesem Ding benachrichtigt und möglicherweise unterbrochen, wenn die Antwort verfügbar ist, damit Sie mit der anderen Operation fortfahren können brauchte es.
Es ist eine seltsame Terminologie. Das macht nicht viel Sinn, da all diese Lösungen eine asynchrone Koordination abhängiger Aufgaben ermöglichen. Deshalb nenne ich es lieber ereignisreich.
In diesem Fall entscheiden Sie sich, Ihren Toaster zu aktualisieren, damit er piept, wenn der Toast fertig ist. Sie hören ständig zu, auch wenn Sie Abwasch machen. Wenn Sie den Piepton hören, stehen Sie in Ihrer Erinnerung an, dass Sie anhalten und die Butter auf den Toast legen, sobald Sie mit dem Waschen Ihres aktuellen Gerichts fertig sind. Oder Sie können das Waschen des aktuellen Gerichts unterbrechen und sich sofort um den Toast kümmern.
Wenn Sie Probleme haben, den Piepton zu hören, können Sie Ihren Partner den Toaster für Sie ansehen lassen und Ihnen mitteilen, wann der Toast fertig ist. Ihr Partner kann selbst eine der drei oben genannten Strategien auswählen, um seine Aufgabe zu koordinieren, den Toaster zu beobachten und Ihnen mitzuteilen, wann er bereit ist.
Abschließend ist es gut zu verstehen, dass Sie, obwohl nicht blockierend und asynchron (oder was ich lieber als ereignisreich bezeichne), andere Dinge tun können, während Sie warten, dies aber nicht tun. Sie können festlegen, dass der Status eines nicht blockierenden Anrufs ständig wiederholt wird, ohne dass dies erforderlich ist. Das ist jedoch oft schlimmer als das Blockieren (wie das Betrachten des Toasters, dann weg, dann wieder zurück, bis es fertig ist), sodass Sie mit vielen nicht blockierenden APIs von dort in einen Blockierungsmodus wechseln können. Bei Ereignissen können Sie einfach im Leerlauf warten, bis Sie benachrichtigt werden. Der Nachteil in diesem Fall ist, dass das Hinzufügen der Benachrichtigung zunächst komplex und möglicherweise kostspielig war. Sie mussten einen neuen Toaster mit Signaltonfunktion kaufen oder Ihren Partner davon überzeugen, ihn für Sie anzusehen.
Und noch etwas: Sie müssen die Kompromisse erkennen, die alle drei bieten. Einer ist offensichtlich nicht besser als die anderen. Denken Sie an mein Beispiel. Wenn Ihr Toaster so schnell ist, haben Sie keine Zeit, ein Gericht zu spülen, und beginnen nicht einmal damit, es zu waschen. So schnell ist Ihr Toaster. In diesem Fall ist es nur eine Verschwendung von Zeit und Mühe, mit etwas anderem zu beginnen. Das Blockieren reicht aus. Wenn das Waschen eines Gerichts 10-mal länger dauert als das Toasten. Sie müssen sich fragen, was wichtiger ist, um fertig zu werden? Der Toast könnte zu diesem Zeitpunkt kalt und hart werden, es lohnt sich nicht, das Blockieren reicht auch aus. Oder Sie sollten schnellere Aufgaben auswählen, während Sie warten. Es ist offensichtlicher, aber meine Antwort ist schon ziemlich lang. Mein Punkt ist, dass Sie über all das nachdenken müssen und über die Komplexität der Implementierung, um zu entscheiden, ob es sich lohnt und ob es sich lohnt. '
Bearbeiten:
Obwohl dies bereits lang ist, möchte ich, dass es vollständig ist, also füge ich zwei weitere Punkte hinzu.
1) Es gibt üblicherweise auch ein viertes Modell, das als Multiplex bekannt ist . Dies ist der Fall, wenn Sie auf eine Aufgabe warten, eine andere starten und während Sie auf beide warten, eine weitere starten und so weiter, bis Sie viele Aufgaben gestartet haben und dann im Leerlauf warten, aber auf alle Sie. Sobald dies erledigt ist, können Sie mit der Bearbeitung der Antwort fortfahren und dann wieder auf die anderen warten. Es wird als Multiplex bezeichnet, da Sie während des Wartens jede Aufgabe nacheinander überprüfen müssen, um festzustellen, ob sie ad vitam erledigt ist, bis eine erledigt ist. Es ist eine Art Erweiterung über das normale Nicht-Blockieren hinaus.
In unserem Beispiel wäre es so, als würde man den Toaster starten, dann den Geschirrspüler, dann die Mikrowelle usw. Und dann auf einen von ihnen warten. Wo Sie den Toaster überprüfen würden, um festzustellen, ob er fertig ist, wenn nicht, würden Sie den Geschirrspüler überprüfen, wenn nicht, die Mikrowelle und wieder herum.
2) Obwohl ich glaube, dass es ein großer Fehler ist, wird synchron oft verwendet, um jeweils eine Sache zu bedeuten. Und viele Dinge gleichzeitig asynchron. Daher sehen Sie synchrones Blockieren und Nicht-Blockieren, um sich auf Blockieren und Nicht-Blockieren zu beziehen. Und asynchrones Blockieren und Nicht-Blockieren wird verwendet, um auf Multiplex und Evented zu verweisen.
Ich verstehe nicht wirklich, wie wir dorthin gekommen sind. Wenn es jedoch um E / A und Berechnung geht, beziehen sich synchron und asynchron häufig auf das, was besser als nicht überlappend und überlappend bekannt ist. Das heißt, asynchron bedeutet, dass sich E / A und Berechnung überlappen, auch bekannt als gleichzeitig. Während synchron bedeutet, dass dies nicht der Fall ist, geschieht dies nacheinander. Bei synchroner Nichtblockierung bedeutet dies, dass Sie keine anderen E / A-Vorgänge oder Berechnungen starten. Sie müssen nur warten und einen blockierenden Anruf simulieren. Ich wünschte, die Leute würden aufhören, so synchron und asynchron zu missbrauchen. Also ermutige ich es nicht.
Anruf blockieren : Die Steuerung wird erst zurückgegeben, wenn der Anruf abgeschlossen ist.
Nicht blockierender Anruf: Die Steuerung kehrt sofort zurück. Das spätere Betriebssystem benachrichtigt den Prozess irgendwie, dass der Anruf abgeschlossen ist.
Synchrones Programm: Ein Programm, das Blocking Calls verwendet. Um während des Aufrufs nicht einzufrieren, müssen 2 oder mehr Threads vorhanden sein (deshalb heißt es Synchron - Threads werden synchron ausgeführt).
Asynchrones Programm: Ein Programm, das nicht blockierende Aufrufe verwendet. Es kann nur 1 Thread haben und trotzdem interaktiv bleiben.
Sie unterscheiden sich nur in der Schreibweise. Es gibt keinen Unterschied in dem, worauf sie sich beziehen. Um technisch zu sein, könnte man sagen, dass sie sich in ihrer Betonung unterscheiden. Nicht blockieren bezieht sich auf den Kontrollfluss (er blockiert nicht). Asynchron bezieht sich auf den Zeitpunkt, zu dem das Ereignis \ data behandelt wird (nicht synchron).
Bei den Blockierungsmodellen muss die initiierende Anwendung blockieren, wenn die E / A gestartet wurde. Dies bedeutet, dass es nicht möglich ist, Verarbeitung und E / A gleichzeitig zu überlappen. Das synchrone nicht blockierende Modell ermöglicht eine Überlappung von Verarbeitung und E / A, erfordert jedoch, dass die Anwendung den Status der E / A regelmäßig überprüft. Dadurch bleibt eine asynchrone, nicht blockierende E / A erhalten, die eine Überlappung von Verarbeitung und E / A ermöglicht, einschließlich einer Benachrichtigung über den Abschluss der E / A.
Blockieren: Die Steuerung kehrt nach Abschluss der Verarbeitung des Grundelements (Synchronisierung oder Asynchronisierung) zum Aufrufen des Vorgangs zurück
Nicht blockierend: Die Steuerung kehrt unmittelbar nach dem Aufruf zum Prozess zurück