Wie geht die Kellnerin mit gleichzeitigen Aufgaben um?

8

Ich versuche, einen Python-Webserver mit Django und Waitress zu erstellen, möchte aber wissen, wie Waitress mit gleichzeitigen Anforderungen umgeht und wann Blockierungen auftreten können.


Während in der Waitress-Dokumentation erwähnt wird, dass mehrere Worker-Threads verfügbar sind, enthält sie nicht viele Informationen darüber, wie sie implementiert sind und wie sich die Python-GIL auf sie auswirkt (Hervorhebung meiner eigenen):

Wenn ein Kanal feststellt, dass der Client mindestens eine vollständig gültige HTTP-Anforderung gesendet hat, plant er eine "Aufgabe" mit einem "Thread-Dispatcher". Der Thread-Dispatcher verwaltet einen festen Pool von Worker-Threads, die für die Client-Arbeit verfügbar sind (standardmäßig 4 Threads). Wenn ein Arbeitsthread verfügbar ist, wenn eine Aufgabe geplant ist, führt der Arbeitsthread die Aufgabe aus. Die Task hat Zugriff auf den Kanal und kann in den Ausgabepuffer des Kanals zurückschreiben. Wenn alle Arbeitsthreads verwendet werden , warten geplante Aufgaben in einer Warteschlange darauf, dass ein Arbeitsthread verfügbar wird.

Es scheint auch nicht viele Informationen über Stackoverflow zu geben. Aus der Frage "Ist Gunicorns gthread asynchroner Arbeiter analog zu Kellnerin?" ::

Die Kellnerin verfügt über einen asynchronen Master-Thread, der Anforderungen puffert und jede Anforderung nach Abschluss der Anforderungs-E / A in einen ihrer Sync-Worker-Threads einreiht.


Diese Aussagen richten sich nicht an die GIL (zumindest nach meinem Verständnis), und es wäre großartig, wenn jemand näher darauf eingehen könnte, wie Arbeitsthreads für Waitress funktionieren. Vielen Dank!

Geschmolzene Muffins
quelle
Haben Sie eine Lösung dafür gefunden?
Variable
@variable Leider nicht. Wenn man sich das Github-Repo der Kellnerin kurz ansieht , scheint es nicht so, als hätten sie irgendetwas getan, um die GIL zu umgehen , obwohl ich nicht sicher sagen kann. Im Moment bleibt mein Team bei Waitress, da unsere App kein zu hohes Maß an Parallelität erfordert.
MoltenMuffins
Bei Verwendung des Standard-Dev-Flask-Servers können wir die Anzahl der Prozesse mithilfe von werkzeug.palletsprojects.com/de/1.0.x/serving/… festlegen. Ist dies in der Kellnerin nicht vorhanden?
Variable
Ja, die Anzahl der Worker kann konfiguriert werden, aber dies sagt nichts über ihr Blockierungsverhalten aus
MoltenMuffins
Wenn ein Worker einen unabhängigen Prozess meint, bedeutet dies, dass jeder Prozess seinen eigenen Python-Interpreter hat. ist es nicht?
Variable

Antworten:

1

So funktionieren die ereignisgesteuerten asynchronen Server im Allgemeinen:

  • Starten Sie einen Prozess und hören Sie eingehende Anfragen ab. Die Verwendung der Ereignisbenachrichtigungs-API des Betriebssystems macht es sehr einfach, Tausende von Clients von einem einzelnen Thread / Prozess aus zu bedienen.
  • Da es nur einen Prozess gibt, der alle Verbindungen verwaltet, möchten Sie in diesem Prozess keine langsamen (oder blockierenden) Aufgaben ausführen. Denn dann wird das Programm für jeden Client blockiert.
  • Um blockierende Aufgaben auszuführen, delegiert der Server die Aufgaben an "Worker". Worker können Threads (die im selben Prozess ausgeführt werden) oder separate Prozesse (oder Unterprozesse) sein. Jetzt kann der Hauptprozess weiterhin Clients bedienen, während Mitarbeiter die Blockierungsaufgaben ausführen.

Wie geht die Kellnerin mit gleichzeitigen Aufgaben um?

Ziemlich genau so, wie ich es oben beschrieben habe. Und für Arbeiter werden Threads erstellt, keine Prozesse.

wie die Python-GIL sie beeinflusst

Kellnerin verwendet Threads für Arbeiter. Ja, sie sind von GIL betroffen, da sie nicht wirklich gleichzeitig sind, obwohl sie zu sein scheinen. "Asynchron" ist der richtige Begriff.

Threads in Python werden in einem einzelnen Prozess auf einem einzelnen CPU-Kern ausgeführt und nicht parallel. Ein Thread erfasst die GIL für eine sehr kurze Zeit und führt seinen Code aus. Anschließend wird die GIL von einem anderen Thread erfasst.

Da die GIL jedoch für Netzwerk-E / A freigegeben wird, erfasst der übergeordnete Prozess die GIL immer dann, wenn ein Netzwerkereignis (z. B. eine eingehende Anforderung) vorliegt. Auf diese Weise können Sie sicher sein, dass die GIL die netzwerkgebundenen Vorgänge nicht beeinträchtigt ( wie das Empfangen von Anfragen oder das Senden von Antworten).

Auf der anderen Seite sind Python-Prozesse tatsächlich gleichzeitig: Sie können auf mehreren Kernen parallel ausgeführt werden. Die Kellnerin verwendet jedoch keine Prozesse.

Solltest du dir Sorgen machen?

Wenn Sie nur kleine Blockierungsaufgaben wie das Lesen / Schreiben von Datenbanken ausführen und nur einige hundert Benutzer pro Sekunde bedienen, ist die Verwendung von Threads nicht wirklich schlecht.

Wenn Sie eine große Anzahl von Benutzern bedienen oder lange laufende Blockierungsaufgaben ausführen möchten, können Sie externe Aufgabenwarteschlangen wie Sellerie verwenden . Dies ist viel besser als das Laichen und Verwalten von Prozessen selbst.

Xyres
quelle
Ist es besser, einen prozessbasierten App-Server zu verwenden, um mehr Anforderungen zu verarbeiten?
Variable
@variable Wenn Sie CPU-gebundene Aufgaben (auch Blockierungsaufgaben genannt) wie umfangreiche Berechnungen ausführen, ist die Verwendung von Prozessarbeitern besser. Es gibt jedoch Projekte wie Sellerie, mit denen Sie blockierende Aufgaben in separaten "Aufgabenwarteschlangen" ausführen können. Es spielt also keine Rolle, welche Art von App-Server Sie verwenden. Aber nur für netzwerkgebundene Aufgaben (wie das Warten auf Clientanforderungen oder das Abrufen von Daten von der API eines Drittanbieters) benötigen Sie keine Mitarbeiter.
Xyres
@variable Und wenn Sie mit "prozessbasiertem" Server einen Server gemeint haben, der für jede Anforderung einen neuen Prozess erstellt, dann ist dies der am wenigsten skalierbare Weg. Der effizienteste (und gebräuchlichste) Weg ist der, den ich oben in der Antwort beschrieben habe: Alle Anforderungen eines einzelnen Hauptprozesses bedienen und Blockierungsaufgaben an Mitarbeiter (Threads oder Unterprozesse) delegieren.
Xyres
Mit "Blockierungsaufgaben an Mitarbeiter delegieren (Threads oder Unterprozesse)" - meinen Sie Sellerie?
Variable
@variable Sie können selbst einen Pool von Unterprozessen in Ihrem Programm verwalten und ihnen die Blockierungsaufgaben übergeben. Für kleinere Projekte ist dieser Ansatz in Ordnung. Sellerie bietet Ihnen den Vorteil einer einfachen Skalierbarkeit. Sie können es je nach Bedarf problemlos auf einem einzelnen Server oder einem Cluster von Servern ausführen. Bei kleineren Projekten kann dies jedoch ein Overkill sein. Sie können bei Bedarf zu Sellerie wechseln.
Xyres