Wann würden Sie Hunderttausende von Threads benötigen?

31

Erlang, Go und Rust behaupten alle auf die eine oder andere Weise, dass sie die gleichzeitige Programmierung mit billigen "Threads" / Coroutinen unterstützen. In den Go-FAQ heißt es:

Es ist praktisch, Hunderttausende von Goroutinen im selben Adressraum zu erstellen.

Das Rust Tutorial sagt:

Da die Erstellung von Tasks erheblich kostengünstiger ist als bei herkömmlichen Threads, kann Rust auf einem typischen 32-Bit-System Hunderttausende von gleichzeitigen Tasks erstellen.

In der Dokumentation von Erlang heißt es:

Die standardmäßige anfängliche Heap-Größe von 233 Wörtern ist recht konservativ, um Erlang-Systeme mit Hunderttausenden oder sogar Millionen von Prozessen zu unterstützen.

Meine Frage: Welche Art von Anwendung erfordert so viele gleichzeitige Ausführungsthreads? Nur die am stärksten ausgelasteten Webserver empfangen sogar Tausende von gleichzeitigen Besuchern. Bewerbungen vom Typ Chef-Arbeiter / Job-Dispatcher, die ich geschrieben habe, geben immer weniger zurück, wenn die Anzahl der Threads / Prozesse viel größer ist als die Anzahl der physischen Kerne. Ich nehme an, es mag für numerische Anwendungen sinnvoll sein, aber in Wirklichkeit delegieren die meisten Leute Parallelität zu Bibliotheken von Drittanbietern, die in Fortran / C / C ++ geschrieben sind, nicht zu diesen Sprachen der neueren Generation.

user39019
quelle
5
Ich denke, die Quelle Ihrer Verwirrung ist folgende: Diese Mikrothreads / Aufgaben / usw. sind nicht primär als Ersatz für die Betriebssystem-Threads / -Prozesse gedacht, über die Sie sprechen, und sie sind auch nicht dazu gedacht, einen leicht parallelisierbaren großen Teil der Zahlenverarbeitung aufzuteilen zwischen ein paar Kernen (wie Sie richtig bemerkt haben, gibt es keinen Grund, zu diesem Zweck 100k-Threads auf 4 Kernen zu haben).
us2012
1
Wofür sind sie dann gedacht? Vielleicht bin ich naiv, aber ich bin nie auf eine Situation gestoßen, in der die Einführung von Coroutines / etc ein Single-Thread-of-Execution-Programm vereinfacht hätte. Unter Linux kann ich Hunderte oder Tausende von Prozessen starten, ohne ins Schwitzen zu geraten.
user39019
Es wäre wenig sinnvoll, so viele Aufgaben tatsächlich zu erledigen. Das bedeutet nicht, dass Sie nicht eine große Anzahl von Aufgaben haben können, die meist nur blockiert wurden, um darauf zu warten, dass etwas passiert.
Loren Pechtel
5
Die Idee der aufgabenbasierten Asynchronität im Vergleich zur threadbasierten Asynchronität besteht darin, dass sich der Benutzercode auf die Aufgaben konzentrieren sollte , die ausgeführt werden müssen, anstatt die Worker zu verwalten , die diese Aufgaben ausführen. Stellen Sie sich einen Faden als einen Arbeiter vor, den Sie einstellen. Die Einstellung eines Arbeitnehmers ist teuer, und wenn Sie dies tun, möchten Sie, dass er 100% der Zeit so viele Aufgaben wie möglich ausführt. Viele Systeme können so charakterisiert werden, dass sie Hunderte oder Tausende von ausstehenden Aufgaben haben, aber Sie brauchen keine Hunderte oder Tausende von Arbeitern.
Eric Lippert
Wenn Sie @ EricLipperts Kommentar fortsetzen, gibt es mehrere Situationen, in denen Hunderttausende von Aufgaben existieren würden. Beispiel Nr. 1: Die Zerlegung einer datenparallelen Aufgabe, z. B. Bildverarbeitung. Beispiel 2: Ein Server, der Hunderttausende von Clients unterstützt, von denen jeder jederzeit einen Befehl ausgeben kann. Jede Aufgabe hätte ihren eigenen "einfachen Ausführungskontext" benötigt - die Fähigkeit, sich zu merken, in welchem ​​Zustand sie ist (Kommunikationsprotokolle), und den Befehl, den sie gerade ausführt, und wenig anderes. Leichtgewicht ist möglich, solange jeder einen flachen Aufrufstapel hat.
Rwong

Antworten:

19

Ein Anwendungsfall - Websockets:
Da Websockets im Vergleich zu einfachen Anforderungen langlebig sind, werden sich auf einem ausgelasteten Server im Laufe der Zeit viele Websockets ansammeln. Mikrothreads bieten eine gute konzeptionelle Modellierung und eine relativ einfache Implementierung.

Im Allgemeinen sollten Fälle, in denen zahlreiche mehr oder weniger autonome Einheiten auf bestimmte Ereignisse warten, gute Anwendungsfälle sein.

kr1
quelle
15

Es könnte hilfreich sein, über das nachzudenken, wofür Erlang ursprünglich entwickelt wurde, nämlich die Verwaltung der Telekommunikation. Aktivitäten wie Routing, Switching, Sensorsammlung / -aggregation usw.

Bringen Sie dies in die Web-Welt - betrachten Sie ein System wie Twitter . Das System würde beim Generieren von Webseiten wahrscheinlich keine Mikrothreads verwenden, aber es könnte sie beim Sammeln / Zwischenspeichern / Verteilen von Tweets verwenden.

Dieser Artikel könnte Ihnen weiterhelfen.

Dave Clausen
quelle
11

In einer Sprache, in der Sie keine Variablen ändern dürfen, erfordert das einfache Beibehalten des Status einen separaten Ausführungskontext (den die meisten Benutzer als Thread und Erlang als Prozess bezeichnen). Grundsätzlich ist alles ein Arbeiter.

Betrachten Sie diese Erlang-Funktion, die einen Zähler verwaltet:

counter(Value) ->
    receive                               % Sit idle until a message is received
        increment -> counter(Value + 1);  % Restart with incremented value
        decrement -> counter(Value - 1);  % Restart with decremented value
        speak     ->
            io:fwrite("~B~n", [Value]),
            counter(Value);               % Restart with unaltered value
        _         -> counter(Value)       % Anything else?  Do nothing.
    end.

In einer herkömmlichen OO-Sprache wie C ++ oder Java würden Sie dies erreichen, indem Sie eine Klasse mit einem privaten Klassenmitglied, öffentlichen Methoden zum Abrufen oder Ändern ihres Status und einem instanziierten Objekt für jeden Leistungsindikator haben. Erlang ersetzt den Begriff des instanziierten Objekts durch einen Prozess, den Begriff der Methoden mit Nachrichten und die Aufrechterhaltung des Status durch Tail-Aufrufe , die die Funktion mit den Werten neu starten, die den neuen Status ausmachen. Der verborgene Vorteil dieses Modells - und der größte Teil von Erlangs Daseinsberechtigung - besteht darin, dass die Sprache den Zugriff auf den Zählerwert mithilfe einer Nachrichtenwarteschlange automatisch serialisiert, sodass der gleichzeitige Code sehr einfach und mit einem hohen Maß an Sicherheit implementiert werden kann .

Sie sind wahrscheinlich daran gewöhnt, dass Kontextwechsel teuer sind, was aus Sicht des Host-Betriebssystems immer noch zutrifft. Die Erlang-Laufzeit ist selbst ein kleines Betriebssystem, das so optimiert ist, dass das Wechseln zwischen den eigenen Prozessen schnell und effizient vonstatten geht, während die Anzahl der Kontextwechsel, die das Betriebssystem durchführt, auf ein Minimum reduziert wird. Aus diesem Grund ist es kein Problem und wird empfohlen, viele tausend Prozesse durchzuführen.

Blrfl
quelle
1
Ihre letzte Anwendung von counter/1sollte einen Kleinbuchstaben verwenden. C;) Ich habe versucht, das Problem zu beheben, aber StackExchange mag keine 1-Zeichen-Änderungen.
d11wtq
4

Meine Frage: Welche Art von Anwendung erfordert so viele gleichzeitige Ausführungsthreads?

1) Die Tatsache, dass eine Sprache "skaliert", bedeutet, dass die Wahrscheinlichkeit geringer ist, dass Sie diese Sprache fallen lassen müssen, wenn die Dinge später komplexer werden. (Dies wird als "Gesamtprodukt" -Konzept bezeichnet.) Viele Leute werfen Apache aus genau diesem Grund für Nginx in den Sand. Wenn Sie sich der "harten Grenze" nähern, die der Thread-Overhead auferlegt, werden Sie ängstlich und überlegen, wie Sie daran vorbeikommen können. Websites können niemals vorhersagen, wie viel Datenverkehr sie erhalten werden. Daher ist es sinnvoll, ein wenig Zeit darauf zu verwenden, die Dinge skalierbar zu machen.

2) Eine Goroutine pro Anfrage nur zu Beginn. Es gibt viele Gründe, Goroutinen intern zu verwenden.

  • Stellen Sie sich eine Web-App mit 100 gleichzeitigen Anforderungen vor, aber jede Anforderung generiert 100 Back-End-Anforderungen. Das offensichtliche Beispiel ist ein Suchmaschinenaggregator. Aber so ziemlich jede App könnte Goroutinen für jeden "Bereich" auf dem Bildschirm erstellen und sie dann unabhängig und nicht nacheinander generieren. Zum Beispiel besteht jede Seite auf Amazon.com aus mehr als 150 Back-End-Anfragen, die nur für Sie zusammengestellt wurden. Sie bemerken es nicht, weil sie parallel und nicht sequentiell sind und jeder "Bereich" ein eigener Webdienst ist.
  • Betrachten Sie eine App, bei der Zuverlässigkeit und Latenz von größter Bedeutung sind. Sie möchten wahrscheinlich, dass jede eingehende Anforderung einige Back-End-Anforderungen auslöst und die zuerst zurückgegebenen Daten zurückgibt .
  • Betrachten Sie jeden "Client-Beitritt" in Ihrer App. Anstatt "für jedes Element Daten abrufen" zu sagen, können Sie eine Reihe von Goroutinen abspalten. Wenn Sie eine Reihe von Slave-DBs abfragen müssen, werden Sie auf magische Weise N Mal schneller. Wenn Sie dies nicht tun, wird es nicht langsamer.

Wenn die Anzahl der Threads / Prozesse viel größer als die Anzahl der physischen Kerne ist, führt dies zu einem Rückgang der Treffer

Die Leistung ist nicht der einzige Grund, ein Programm in CSP aufzulösen . Es kann das Programm tatsächlich verständlicher machen und einige Probleme können mit viel weniger Code gelöst werden.

Wie in den oben verlinkten Folien ist die gleichzeitige Verwendung von Code eine Möglichkeit, das Problem zu organisieren. Das Fehlen von Goroutinen entspricht dem Fehlen einer Karten-, Diktier- oder Hash-Datenstruktur in Ihrer Sprache. Sie können ohne auskommen. Aber sobald Sie es haben, können Sie es überall verwenden, und es vereinfacht Ihr Programm wirklich.

In der Vergangenheit bedeutete dies "Roll your own" Multithread-Programmierung. Aber das war komplex und gefährlich - es gibt immer noch nicht viele Tools, mit denen Sie sicherstellen können, dass Sie keine Rennen veranstalten. Und wie verhindern Sie, dass ein zukünftiger Betreuer einen Fehler macht? Wenn Sie sich große / komplexe Programme ansehen, werden Sie feststellen, dass sie eine Menge Ressourcen in diese Richtung verbrauchen .

Da Parallelität in den meisten Sprachen kein erstklassiger Bestandteil ist, haben die heutigen Programmierer einen blinden Fleck, warum sie für sie nützlich sein sollten. Dies wird erst deutlicher, wenn jedes Telefon und jede Armbanduhr auf 1000 Kerne zusteuert. Gehen Sie mit einem eingebauten Race-Detector-Tool auf Schiff.

BraveNewCurrency
quelle
2

Für Erlang ist es üblich, einen Prozess pro Verbindung oder andere Aufgabe zu haben. So kann beispielsweise ein Streaming-Audioserver einen Prozess pro verbundenem Benutzer haben.

Die Erlang VM ist für die Verarbeitung von Tausenden oder sogar Hunderttausenden von Prozessen optimiert, indem Kontextwechsel sehr kostengünstig vorgenommen werden.

Zachary K
quelle
1

Bequemlichkeit. Als ich anfing, Multithread-Programmierung zu machen, habe ich zum Spaß nebenbei eine Menge Simulationen und Spieleentwicklungen durchgeführt. Ich fand es sehr praktisch, einfach einen Thread für jedes einzelne Objekt abzuspinnen und es seine eigene Sache machen zu lassen, anstatt jeden einzelnen durch eine Schleife zu verarbeiten. Wenn Ihr Code nicht durch nicht deterministisches Verhalten gestört wird und Sie keine Kollisionen haben, kann dies die Codierung erleichtern. Mit der uns jetzt zur Verfügung stehenden Energie kann ich mir leicht vorstellen, ein paar tausend Threads abzuspulen, da ich genug Rechenleistung und Speicher habe, um mit so vielen diskreten Objekten umzugehen!

Brian Knoblauch
quelle
1

Ein einfaches Beispiel für Erlang, das für die Kommunikation konzipiert wurde: die Übertragung von Netzwerkpaketen. Wenn Sie eine http-Anforderung ausführen, verfügen Sie möglicherweise über Tausende von TCP / IP-Paketen. Fügen Sie dies hinzu, dass alle gleichzeitig eine Verbindung herstellen und Sie Ihren Anwendungsfall haben.

Betrachten Sie viele Anwendungen, die von großen Unternehmen intern verwendet werden, um ihre Aufträge zu bearbeiten oder was auch immer sie benötigen. Webserver sind nicht das einzige, was Threads benötigt.

Florian Margaine
quelle
-2

Hier fallen Ihnen einige Rendering-Aufgaben ein. Wenn Sie eine lange Reihe von Operationen auf jedem Pixel eines Bildes ausführen und diese Operationen parallelisierbar sind, dann befindet sich sogar ein relativ kleines 1024x768-Bild in der Klammer "Hunderttausende".

Maximus Minimus
quelle
2
Vor einigen Jahren habe ich einige Jahre damit verbracht, FLIR-Bilder in Echtzeit zu verarbeiten und dabei 256 x 256 Bilder mit 30 Bildern pro Sekunde zu verarbeiten. Wenn Sie nicht über eine Vielzahl von HARDWARE-Prozessoren verfügen und Ihre Daten auf NAHTLOSE Weise auf diese Prozessoren aufteilen können, müssen Sie den tatsächlichen Rechenaufwand als Letztes um Kontextwechsel, Speicherkonflikte und Cache-Thrashing erweitern.
John R. Strohm
Es kommt auf die Arbeit an. Wenn Sie lediglich einen Auftrag an einen Hardware-Kern / eine Ausführungseinheit übergeben und ihn / sie anschließend effektiv vergessen können (und beachten Sie, dass GPUs auf diese Weise funktionieren, sodass dies kein hypothetisches Szenario ist), lautet der Ansatz gültig.
Maximus Minimus