4 Punkte von GN⁺ 2024-03-26 | 1 Kommentare | Auf WhatsApp teilen
  • In der Rust-Community sieht man häufig die Frage: Wenn Threads alles können, was async/await kann, und dazu noch einfacher sind, warum entscheidet man sich dann für async/await?
  • Rust ist eine Low-Level-Sprache und verbirgt die Komplexität von Coroutines nicht. Das ist das Gegenstück zu Sprachen wie Go, die standardmäßig asynchron arbeiten, ohne dass Programmierer Asynchronität überhaupt berücksichtigen müssen.
  • Clevere Programmierer versuchen, Komplexität zu vermeiden — warum also braucht man async/await?

Den Hintergrund verstehen

  • Rust ist eine Low-Level-Sprache. Code ist normalerweise linear: Wenn eine Aufgabe endet, wird die nächste ausgeführt.
  • Wenn jedoch viele Aufgaben gleichzeitig ausgeführt werden müssen, etwa in einem Webserver, wird linearer Code zum Problem.
  • Das frühe Web versuchte, dieses Problem durch die Einführung von Threading zu lösen.
  • Mit Threads lassen sich mehrere Clients gleichzeitig bedienen, doch Programmierer wollten Nebenläufigkeit aus dem OS-Space in den User-Space holen.

Das Timeout-Problem

  • Einer der größten Vorteile von Rust ist die Komponierbarkeit (composability).
  • async/await ermöglicht es, diese Komponierbarkeit auch auf I/O-gebundene Funktionen anzuwenden.
  • Wenn man zum Beispiel einer Client-Handler-Funktion ein Timeout hinzufügen möchte, lässt sich das mit zwei Combinators umsetzen.

Thematische Threads

  • In einem Beispiel auf Basis von Threads ist ein Timeout nicht leicht umzusetzen.
  • TcpStream hat zwar die Funktionen set_read_timeout und set_write_timeout, ihre Verwendung ist aber eingeschränkt.
  • Es wird gezeigt, wie sich Timeouts mit Rust-Combinators programmieren lassen, doch das ist auf TcpStream beschränkt und erfordert zusätzliche Systemaufrufe.

Erfolgsbeispiele für Async

  • Das HTTP-Ökosystem hat async/await als zentralen Runtime-Mechanismus übernommen.
  • tower ist ein Beispiel für die Stärke von async/await und bietet Timeouts, Rate Limiting, Load Balancing und mehr.
  • macroquad ist eine Rust-Game-Engine, die ihre Engine mit async/await ausführt.

Das Image von Async verbessern

  • Die Vorteile von async sind nicht allgemein bekannt, weshalb manche Menschen sie missverstehen können.
  • Die Rust-Community neigt dazu, die Performance-Vorteile von async Rust zu überschätzen und seine eigentlichen, bedeutenden Vorteile zu kleinzureden.
  • async/await sollte als starkes Programmiermodell gesehen werden, das Muster prägnant ausdrücken kann, die sich in synchronem Rust ohne Dutzende Threads und Channels nicht formulieren lassen.

Meinung von GN⁺

  • async/await erhöht beim Umgang mit Nebenläufigkeit zwar die Komplexität des Codes, bietet aber zugleich die Fähigkeit, sehr viele Clients effizient zu bedienen.
  • Dieser Artikel betont, dass async/await nicht nur Vorteile bei der Performance bietet, sondern auch Stärken als Programmiermodell hat.
  • Rusts async/await bietet Komponierbarkeit für verschiedenste I/O-Aufgaben, was besonders in Bereichen wie Netzwerkdiensten oder Webservern nützlich ist.
  • Kritisch betrachtet kann die Komplexität von async/await für Einsteiger eine Einstiegshürde sein, weshalb Bildungs- und Vermittlungsarbeit nötig ist.
  • Andere Projekte mit ähnlicher Funktionalität sind etwa die async/await-Implementierung von Node.js oder die asyncio-Bibliothek von Python, die ein ähnliches Paradigma bieten.
  • Bei der Einführung von async/await sollten die Komplexität des Codes und die Wartbarkeit berücksichtigt werden; wenn jedoch viele Clients gleichzeitig verarbeitet werden müssen, bietet dieses Modell große Vorteile.

1 Kommentare

 
GN⁺ 2024-03-26
Hacker-News-Kommentare
  • Async/await und Single-Threading

    • Async/await auf einem einzelnen Thread ist – wie im JavaScript-Modell – einfach und gut verstanden.
    • Mit Threads können mehrere CPUs ein Problem bearbeiten, und Rust hilft beim Verwalten von Locks.
    • Man kann Threads mit unterschiedlichen Prioritäten haben; das ist nötig, wenn Berechnungen begrenzt werden müssen.
    • Multithreaded Async/await ist komplex. In Abschnitten mit begrenzter Rechenzeit kann das Modell zusammenbrechen.
    • Multithreaded-Berechnungen funktionieren in Rust nicht gut. Zu den Problemen gehören:
      • Futex-Contention-Collapse: kann bei einigen Storage-Allokatoren ein Problem sein.
      • Verhungern durch unfaire Mutexe: Der Standard-Mutex und die Kanäle von crossbeam-channel sind unfair.
  • Async/await vs. Threads

    • Die Kritik bezieht sich nicht auf Komplexität, sondern darauf, dass die Wahl das Ökosystem spaltet und eine Option unterlegen macht.
    • Das Rust-Ökosystem hat entschieden, dass man für I/O-Arbeit durchgängig Async/await verwenden muss.
    • Hätte Rust Dinge außerhalb von Async/await zu besser kombinierbaren Abstraktionen gemacht, wäre die Unzufriedenheit verschwunden.
  • Probleme mit dem Artikel

    • Es wurde nur ein Webserver-Beispiel gezeigt, und die Thread-Lösung dafür war falsch.
    • Programmierer wollen konzeptionelle, semantische Threads, nicht OS-Threads.
    • OS-Threads sind teuer; wir wollen billige Threads.
    • Probleme bei der Implementierung von Timeouts im Webserver-Beispiel.
  • Nicht behandelte Aspekte

    • Async/await läuft auf einem einzelnen Thread, daher sind keine Locks oder Synchronisation nötig.
    • Die Fehlerweitergabe bei Async/await ist nicht klar.
    • Auch Backpressure bei Netzwerk-I/O hätte erwähnt werden müssen.
  • Wichtige Punkte zur Abbruchlogik

    • Zukünftige Arbeit lässt sich leicht abbrechen.
    • Abbruch bei Threads ist komplex, und erzwungenes Thread-Stoppen ist unzuverlässig.
    • Im Async-Modell von Rust kann man allen Futures von außen ein Timeout hinzufügen.
  • Eine marketingartige Kampagne für Async/await

    • Async/await war ein technischer Fehler und hat der Community große Kosten verursacht.
    • Rust ist immer noch die beste Sprache, aber es gibt die Sorge, dass diese Debatte ewig weitergeht.
  • Async/await vs. Fiber

    • Rust hatte früher Green Threads und hat sie bewusst entfernt.
    • Die Fähigkeit, Futures jederzeit zu droppen, bringt hohe Kosten mit sich.
    • Es ist seltsam, die Kombinierbarkeit von Async/await zu loben.
  • Wichtigste Vorteile von Async/await in Rust

    • Es kann auch in Umgebungen ohne Threads oder dynamischen Speicher funktionieren.
    • Mit Concurrency lässt sich Code kompakt schreiben.
  • Missverständnisse über Async/await

    • Manche verstehen nicht, warum man einen Concurrency-Mechanismus auf einem einzelnen Thread braucht.
    • Async/await ist nützlich für UI-Programmierung, die Kommunikation mit GPUs und die Kommunikation zwischen Runtimes.
  • Warum Async/await statt Threads wählen

    • Async/await kann den Speicherverbrauch pro Client/Request/Aufgabe senken.
    • Zustandskomprimierung ist auf moderner Hardware mit langsamem Speicher wichtig für die Performance.
    • Async/await und CPS sind wirksam, um den Speicherverbrauch pro Client zu reduzieren.