Was ist der Unterschied zwischen: Asynchronen, nicht blockierenden Event-Base-Architekturen?


84
  1. Was ist der Unterschied zwischen:

    • Asynchron ,
    • Nicht blockierend und
    • Event-basierte Architekturen?
  2. Kann etwas sowohl asynchron als auch nicht blockierend (und ereignisbasiert ) sein?

  3. Was ist beim Programmieren am wichtigsten, um etwas zu haben: asynchron, nicht blockierend und / oder ereignisbasiert (oder alle 3)?

Wenn Sie Beispiele nennen könnten, wäre das großartig.

Diese Frage wird gestellt, weil ich diesen großartigen StackOverflow- Artikel zu einem ähnlichen Thema gelesen habe, aber meine obigen Fragen nicht beantwortet.

Antworten:


91

Asynchron Asynchron bedeutet wörtlich nicht synchron. E-Mail ist asynchron. Wenn Sie eine E-Mail senden, erwarten Sie JETZT keine Antwort. Aber es ist nicht nicht blockierend. Im Wesentlichen bedeutet dies eine Architektur, in der "Komponenten" Nachrichten aneinander senden, ohne sofort eine Antwort zu erwarten. HTTP-Anforderungen sind synchron. Senden Sie eine Anfrage und erhalten Sie eine Antwort.

Nicht blockierend Dieser Begriff wird hauptsächlich bei E / A verwendet. Dies bedeutet, dass ein Systemaufruf sofort mit dem gewünschten Ergebnis zurückgegeben wird, ohne dass der Thread in den Ruhezustand versetzt wird (mit hoher Wahrscheinlichkeit). Zum Beispiel kehren nicht blockierende Lese- / Schreibanrufe mit allem zurück, was sie können, und erwarten, dass der Anrufer den Anruf erneut ausführt. try_lock zum Beispiel ist ein nicht blockierender Aufruf. Es wird nur gesperrt, wenn die Sperre erworben werden kann. Die übliche Semantik für Systemaufrufe blockiert. read wartet, bis einige Daten vorliegen, und versetzt den aufrufenden Thread in den Ruhezustand.

Ereignisbasis Dieser Begriff stammt von libevent. Nicht blockierende Lese- / Schreibaufrufe an sich sind nutzlos, da sie Ihnen nicht sagen, wann Sie sie zurückrufen sollen (erneut versuchen). select / epoll / IOCompletionPort usw. sind verschiedene Mechanismen, um vom Betriebssystem herauszufinden, wann diese Aufrufe "interessante" Daten zurückgeben sollen. libevent und andere solche Bibliotheken bieten Wrapper für diese Ereignisüberwachungsfunktionen, die von verschiedenen Betriebssystemen bereitgestellt werden, und bieten eine konsistente API für die Arbeit, die betriebssystemübergreifend ausgeführt wird. Nicht blockierende E / A gehen Hand in Hand mit Event-Base.

Ich denke, diese Begriffe überschneiden sich. Das HTTP-Protokoll ist beispielsweise synchron, aber die HTTP-Implementierung mit nicht blockierenden E / A kann asynchron sein. Wiederum ist ein nicht blockierender API-Aufruf wie read / write / try_lock synchron (er gibt sofort eine Antwort), aber "Datenverarbeitung" ist asynchron.


2
Ein guter Punkt zum Nicht-Blockieren, der ein ständiges Abrufen erfordert, während Async Push-basiert sein kann.
Alexander Torstling

Sie haben synchron als Empfang einer sofortigen Antwort definiert, aber wenn ich synchron google, definieren alle Wörterbücher dies als "gleichzeitig geschehen" und nicht als "sofortige Antwort".
IntelliData

4
Wie werde ich blockiert, wenn ich eine E-Mail sende, aber keine Antwort erwarte? Ich kann mich um meine eigenen Angelegenheiten kümmern, während ich auf eine Antwort warte.
Koray Tugay

20

In einer asynchronen Hardware fordert der Code eine Entität auf, etwas zu tun, und kann andere Dinge tun, während die Aktion ausgeführt wird. Sobald die Aktion abgeschlossen ist, signalisiert die Entität den Code normalerweise auf irgendeine Weise. Eine nicht blockierende Architektur notiert spontan auftretende Aktionen, an denen Code interessiert sein könnte, und ermöglicht es dem Code, zu fragen, welche Aktionen aufgetreten sind. Code wird jedoch nur dann auf solche Aktionen aufmerksam, wenn er explizit danach fragt. Eine ereignisbasierte Architektur benachrichtigt den Code positiv, wenn Ereignisse spontan auftreten.

Stellen Sie sich eine serielle Schnittstelle vor, von der der Code 1.000 Byte empfangen soll.

In einer Blocking-Read-Architektur wartet der Code, bis entweder 1.000 Bytes eingetroffen sind, oder entscheidet sich, aufzugeben.

In einer Architektur mit asynchronem Lesen teilt der Code dem Treiber mit, dass er 1.000 Byte möchte, und wird benachrichtigt, wenn 1.000 Byte eingetroffen sind.

In einer nicht blockierenden Architektur kann der Code jederzeit fragen, wie viele Bytes angekommen sind, und kann einige oder alle dieser Daten lesen, wenn er dies für richtig hält. Die einzige Möglichkeit, zu erkennen, wann alle Daten angekommen sind, besteht darin, zu fragen. Wenn der Code innerhalb einer Viertelsekunde herausfinden möchte, wann das 1000. Byte eingetroffen ist, muss er etwa jede Viertelsekunde überprüfen.

In einer ereignisbasierten Architektur benachrichtigt der Treiber für die serielle Schnittstelle die Anwendung jedes Mal, wenn Daten eingehen. Der Treiber weiß nicht, wie viele Bytes die Anwendung benötigt, daher muss die Anwendung in der Lage sein, Benachrichtigungen für Beträge zu verarbeiten, die kleiner oder größer sind als von der Anwendung gewünscht.


5

Um Ihre erste und zweite Frage zu beantworten:

Nicht blockieren ist praktisch dasselbe wie asynchron - Sie tätigen den Anruf und erhalten später ein Ergebnis, aber währenddessen können Sie etwas anderes tun. Blockieren ist das Gegenteil. Sie warten auf die Rückkehr des Anrufs, bevor Sie Ihre Reise fortsetzen.

Jetzt klingt Async / Non-Blocking-Code absolut fantastisch und ist es auch. Aber ich habe warnende Worte. Async / Non-Blocking eignet sich hervorragend für Arbeiten in eingeschränkten Umgebungen, z. B. in Mobiltelefonen. Beachten Sie die begrenzte CPU / den begrenzten Arbeitsspeicher. Es ist auch gut für die Front-End-Entwicklung geeignet, bei der Ihr Code auf irgendeine Weise auf ein UI-Widget reagieren muss.

Async ist von grundlegender Bedeutung für die Funktionsweise aller Betriebssysteme - sie erledigen im Hintergrund alles für Sie und wecken Ihren Code, wenn sie das getan haben, wonach Sie gefragt haben, und wenn dieser Aufruf fehlschlägt, wird Ihnen mitgeteilt, dass dies nicht der Fall ist arbeiten entweder mit einer Ausnahme oder einer Art Rückkehrcode / Fehlerobjekt.

An dem Punkt, an dem Ihr Code nach etwas fragt, dessen Beantwortung eine Weile dauern wird, weiß Ihr Betriebssystem, dass es mit anderen Aufgaben beschäftigt sein kann. Ihr Code - ein Prozess, ein Thread oder ein gleichwertiger Block. Ihr Code weiß überhaupt nicht, was sonst noch im Betriebssystem vor sich geht, während er darauf wartet, dass diese Netzwerkverbindung hergestellt wird, oder während er auf diese Antwort von einer HTTP-Anforderung wartet oder während er auf das Lesen / Schreiben einer Datei wartet, und demnächst. Ihr Code könnte "einfach" auf einen Mausklick warten. Während dieser Zeit war es tatsächlich so, dass Ihr Betriebssystem nahtlos "Ereignisse" verwaltet, plant und darauf reagiert - Dinge, auf die das Betriebssystem achtet, wie z. B. Speicherverwaltung, E / A (Tastatur, Maus, Festplatte, Internet), andere Aufgaben, Fehlerbehebung usw.

Betriebssysteme sind verdammt hart im Nehmen. Sie sind wirklich gut darin, all die komplizierten asynchronen / nicht blockierenden Dinge vor Ihnen, dem Programmierer, zu verbergen. Und so kamen die meisten Programmierer mit Software dahin, wo wir heute sind. Jetzt stoßen wir an die CPU-Grenzen. Die Leute sagen, dass Dinge parallel gemacht werden können, um die Leistung zu verbessern. Dies bedeutet, dass Async / Non-Blocking eine sehr günstige Vorgehensweise ist, und ja, wenn Ihre Software dies erfordert, kann ich dem zustimmen.

Wenn Sie einen Back-End-Webserver schreiben, gehen Sie vorsichtig vor. Denken Sie daran, dass Sie für viel billiger horizontal skalieren können. Netflix / Amazon / Google / Facebook sind jedoch offensichtliche Ausnahmen von dieser Regel, nur weil es für sie billiger ist, weniger Hardware zu verwenden.

Ich werde Ihnen sagen, warum asynchroner / nicht blockierender Code ein Albtraum bei Back-End-Systemen ist.

1) Es wird zu einem Denial-of-Service für die Produktivität ... Sie müssen VIEL mehr nachdenken und machen dabei viele Fehler.

2) Stapelspuren in reaktivem Code werden nicht mehr zu entziffern - es ist schwer zu wissen, was was, wann, warum und wie heißt. Viel Glück beim Debuggen.

3) Sie müssen mehr darüber nachdenken, wie Dinge scheitern, insbesondere wenn viele Dinge nicht mehr in der richtigen Reihenfolge sind, wie Sie sie gesendet haben. In der alten Welt haben Sie jeweils eine Sache getan.

4) Es ist schwieriger zu testen.

5) Es ist schwieriger zu pflegen.

6) Es ist schmerzhaft. Das Programmieren soll Freude und Spaß machen. Nur Masochisten mögen Schmerz. Menschen, die gleichzeitig / reaktive Frameworks schreiben, sind Sadisten.

Und ja, ich habe sowohl synchron als auch asynchron geschrieben. Ich bevorzuge synchron, da 99,99 Back-End-Anwendungen mit diesem Paradigma auskommen können. Front-End-Apps benötigen ohne Frage reaktiven Code, und das war schon immer so.

  1. Ja, Code kann asynchron, nicht blockierend UND ereignisbasiert sein.

  2. Das Wichtigste bei der Programmierung ist, sicherzustellen, dass Ihr Code funktioniert und in akzeptabler Zeit reagiert. Halten Sie sich an dieses Schlüsselprinzip und Sie können nichts falsch machen.


** UPDATE ** Nachdem ich mit Go gespielt und mich mit Kanälen und Go-Routinen beschäftigt habe, muss ich sagen, dass ich es wirklich mag, meinen Code gleichzeitig zu gestalten, da die Konstrukte der Sprache den sadistischen Framework-Autoren den ganzen Weg nehmen. Wir haben ein "sicheres Wort" in der Welt der asynchronen Verarbeitung - und das ist "Go!"
user924272

4

Nicht blockierend bedeutet für mich, dass die Ausführung einer Aktion in einem Thread nicht von der Ausführung anderer Threads abhängt, sondern insbesondere keinen kritischen Abschnitt erfordert.

Asynchron bedeutet, dass die Ausführung außerhalb des Anruferflusses erfolgt und möglicherweise verzögert wird. Die Ausführung erfolgt normalerweise in einem anderen Thread.

Das Lesen gleichzeitiger Daten ist nicht blockierend (keine Sperrung erforderlich), jedoch synchron. Umgekehrt blockiert das synchrone gleichzeitige Schreiben von Daten (erfordert eine exklusive Sperre). Eine Möglichkeit, die Blockierung aus Sicht des Hauptflusses zu verhindern, besteht darin, die Schreibvorgänge asynchron zu gestalten und ihre Ausführung zu verschieben.

Das Konzept des Ereignisses ist etwas anderes, was grob gesagt bedeutet, dass Sie informiert werden, wenn etwas passiert. Wenn Schreibvorgänge asynchron ausgeführt wurden, kann ein Ereignis ausgelöst werden, um andere Teile des Systems zu informieren, sobald der Schreibvorgang ausgeführt wurde. Die anderen Teile reagieren auf das Ereignis. Das System kann ausschließlich auf Ereignissen aufgebaut werden, um nur zwischen Komponenten zu kommunizieren (denken Sie an das Akteurmodell), dies muss jedoch nicht unbedingt der Fall sein.

Die drei Begriffe sind verwandt, aber für mich unterschiedliche Konzepte. Es kann jedoch sein, dass die Leute sie auf eine etwas austauschbare Weise verwenden.


2

Im Allgemeinen basiert eine nicht blockierende Architektur auf Methodenaufrufen, die zwar lange im Worker- Thread ausgeführt werden, den aufrufenden Thread jedoch nicht blockieren . Wenn der aufrufende Thread Informationen über oder von der Aufgabe abrufen muss, die der Arbeitsthread ausführt, muss der aufrufende Thread dies tun.

Eine ereignisbasierte Architektur basiert auf dem Konzept, dass Code als Reaktion auf ausgelöste Ereignisse ausgeführt wird. Der Zeitpunkt der Codeausführung ist im Allgemeinen nicht deterministisch, aber Ereignisse können Blockierungsmethoden aufrufen. Nur weil ein System ereignisbasiert ist, bedeutet dies nicht, dass alles, was es tut, nicht blockiert.

Im Allgemeinen ist eine asynchrone Architektur eine ereignisbasierte, nicht blockierende Architektur.

Wenn ein asynchroner Aufruf erfolgt, werden Ereignishandler bei der API registriert, die Synchronisierungsdienste bereitstellt, um den Anrufer darüber zu informieren, dass etwas passiert ist, an dem der Anrufer interessiert ist. Der Anruf kehrt dann sofort zurück (nicht blockierendes Verhalten), und der Anrufer kann die Ausführung fortsetzen. Wenn Ereignisse an den aufrufenden Prozess zurückgegeben werden, werden sie in einem Thread in diesem Prozess behandelt.

Es ist wichtig zu verstehen, ob Ereignisse auf demselben Thread behandelt werden oder nicht, da dies die nicht blockierende Natur der Ausführung beeinflusst. Mir sind jedoch keine Bibliotheken bekannt, die eine asynchrone Ausführungsverwaltung für einen einzelnen Thread durchführen.

Ich habe den obigen Absatz entfernt, weil er nicht wie angegeben korrekt ist. Meine Absicht war es zu sagen, dass, obwohl die Vorgänge im System nicht blockierend sind, wie z. B. das Aufrufen einer Betriebssystemeinrichtung und das Fortführen der Ausführung, die Art der Single-Thread-Ausführung bedeutet, dass Ereignisse ausgelöst werden, wenn sie ausgelöst werden andere Verarbeitungsaufgaben für die Rechenzeit im Thread.


Widerspricht Ihr letzter Absatz nicht Ihrer Aussage, dass "asynchrone Architektur ... nicht blockierend ist"
Nickb

Ich glaube, ich habe den Teil "Definitionen" Ihrer Frage nicht sehr gut angesprochen. Ich werde ein Update veröffentlichen. Nein, bei der Ausführung mit einem Thread wird jede Operation von Natur aus blockiert, während sie ausgeführt wird , was die Asynchronität noch nützlicher macht.
Arootbeer
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.