it-swarm.com.de

Multithreading: Was ist der Sinn von mehr Threads als Kernen?

Ich dachte, der Sinn eines Multicore-Computers ist, dass er mehrere Threads gleichzeitig ausführen kann. Wenn Sie eine Quad-Core-Maschine haben, warum sollten dann mehr als 4 Threads gleichzeitig laufen? Würden sie sich nicht einfach Zeit nehmen?

97
Nick Heiner

Die Antwort dreht sich um den Zweck von Threads, nämlich Parallelität: mehrere separate Ausführungszeilen gleichzeitig auszuführen. In einem "idealen" System würde ein Thread pro Kern ausgeführt: keine Unterbrechung. In Wirklichkeit ist dies nicht der Fall. Selbst wenn Sie vier Kerne und vier Arbeitsthreads haben, werden Ihr Prozess und seine Threads ständig für andere Prozesse und Threads ausgetauscht. Wenn Sie ein modernes Betriebssystem verwenden, hat jeder Prozess mindestens einen Thread und viele weitere. Alle diese Prozesse werden gleichzeitig ausgeführt. Wahrscheinlich laufen gerade mehrere hundert Threads auf Ihrem Computer. Es wird nie eine Situation geben, in der ein Thread ausgeführt wird, ohne dass ihm Zeit "gestohlen" wurde. (Nun, Sie könnten, wenn es in Echtzeit läuft , wenn Sie ein Echtzeit-Betriebssystem verwenden oder sogar unter Windows eine Echtzeit-Thread-Priorität verwenden. Aber das ist selten.) )

Vor diesem Hintergrund lautet die Antwort: Ja, mehr als vier Threads auf einer echten Vier-Kern-Maschine können zu einer Situation führen, in der sie sich gegenseitig die Zeit stehlen , jedoch nur, wenn sie einzeln sind Thread benötigt 100% CPU . Wenn ein Thread nicht zu 100% funktioniert (wie es ein UI-Thread möglicherweise nicht ist oder ein Thread einen kleinen Teil seiner Arbeit erledigt oder auf etwas anderes wartet), ist ein anderer geplanter Thread eine gute Situation.

Es ist tatsächlich komplizierter als das:

  • Was ist, wenn Sie fünf Arbeiten auf einmal erledigen müssen? Es ist sinnvoller, sie alle auf einmal auszuführen, als vier davon und die fünfte später.

  • Es ist selten, dass ein Thread wirklich 100% CPU benötigt. In dem Moment, in dem beispielsweise Festplatten- oder Netzwerk-E/A verwendet werden, kann es sein, dass Sie möglicherweise einige Zeit warten, bis Sie nichts Nützliches mehr tun. Dies ist eine sehr häufige Situation.

  • Wenn Sie Arbeit haben, die ausgeführt werden muss, besteht ein allgemeiner Mechanismus darin, einen Threadpool zu verwenden. Es mag sinnvoll erscheinen, die gleiche Anzahl von Threads wie Kerne zu haben, doch im .Net-Threadpool stehen pro Prozessor bis zu 250 Threads zur Verfügung . Ich bin mir nicht sicher, warum sie das tun, aber meine Vermutung hängt mit der Größe der Aufgaben zusammen, die für die Ausführung der Threads angegeben werden.

Also: Zeit zu stehlen ist keine schlechte Sache (und auch nicht wirklich Diebstahl: So soll das System funktionieren.) Schreiben Sie Ihre Multithread-Programme basierend auf der Art der Arbeit, die die Threads erledigen, die möglicherweise keine CPU sind -gebunden. Ermitteln Sie die Anzahl der benötigten Threads basierend auf Profilerstellung und Messung. Möglicherweise ist es sinnvoller, in Aufgaben oder Jobs zu denken, als in Threads: Schreiben Sie Arbeitsobjekte und geben Sie sie einem Pool, der ausgeführt werden soll. Machen Sie sich nicht zu viele Sorgen, es sei denn, Ihr Programm ist wirklich leistungskritisch :)

65
David

Nur weil ein Thread existiert, heißt das nicht immer, dass er aktiv läuft. In vielen Anwendungen von Threads wird ein Teil der Threads in den Ruhezustand versetzt, bis es Zeit für sie ist, etwas zu tun. Beispielsweise können Benutzereingaben Threads auslösen, die aktiviert werden, einige Verarbeitungsschritte ausführen und wieder in den Ruhezustand wechseln.

Im Wesentlichen handelt es sich bei Threads um einzelne Aufgaben, die unabhängig voneinander arbeiten können, ohne dass der Fortschritt einer anderen Aufgabe erkannt werden muss. Es ist durchaus möglich, mehr davon zu haben, als Sie gleichzeitig laufen können. Sie sind immer noch nützlich, auch wenn sie manchmal hintereinander warten müssen.

49
Amber

Der Punkt ist, dass, obwohl keine reale Beschleunigung erreicht wird, wenn die Thread-Anzahl die Core-Anzahl überschreitet, Sie Threads verwenden können, um Teile der Logik zu entwirren, die nicht voneinander abhängig sein müssen. 

Selbst in einer mäßig komplexen Anwendung macht ein Versuch mit einem einzigen Thread schnell alles zu einem Hash des "Flusses" Ihres Codes. Der einzelne Thread verbringt die meiste Zeit damit, dies abzufragen, dies zu überprüfen und nach Bedarf Routinen aufzurufen, und es wird schwierig, etwas anderes als einen Trottel von Minutien zu erkennen.

Vergleichen Sie dies mit dem Fall, in dem Sie Threads Tasks zuordnen können, sodass Sie bei der Betrachtung jedes einzelnen Threads sehen können, was der Thread tut. Zum Beispiel kann ein Thread das Warten auf Eingaben von einem Socket blockieren, den Stream in Nachrichten analysieren, Nachrichten filtern und, wenn eine gültige Nachricht eingeht, diese an einen anderen Arbeitsthread übergeben. Der Worker-Thread kann Eingaben aus einer Reihe anderer Quellen bearbeiten. Der Code für jeden von ihnen wird einen sauberen, zielgerichteten Fluss zeigen, ohne explizit prüfen zu müssen, dass nichts anderes zu tun ist.

Durch die Partitionierung der Arbeit auf diese Weise kann Ihre Anwendung darauf vertrauen, dass das Betriebssystem plant, was als Nächstes mit der CPU zu tun ist. Sie müssen also nicht überall in Ihrer Anwendung explizit bedingte Überprüfungen darüber durchführen, was blockiert und was zu verarbeiten ist.

24
JustJeff

Wenn ein Thread auf eine Ressource wartet (z. B. Laden eines Werts aus RAM in ein Register, Datenträger-E/A, Netzwerkzugriff, Starten eines neuen Prozesses, Abfragen einer Datenbank oder Warten auf Benutzereingaben), den Prozessor kann an einem anderen Thread arbeiten und zum ersten Thread zurückkehren, sobald die Ressource verfügbar ist. Dies reduziert die Zeit, die die CPU im Leerlauf verbringt, da die CPU Millionen von Operationen ausführen kann, anstatt sich im Leerlauf zu befinden.

Stellen Sie sich einen Thread vor, der Daten von einer Festplatte lesen muss .. Ein typischer Prozessorkern arbeitet im Jahr 2014 mit 2,5 GHz und ist in der Lage, 4 Anweisungen pro Zyklus auszuführen. Mit einer Zykluszeit von 0,4 ns kann der Prozessor 10 Anweisungen pro Nanosekunde ausführen. Bei typischen mechanischen Festplattenlaufzeiten von etwa 10 Millisekunden ist der Prozessor in der Lage, 100 Millionen Befehle in der Zeit auszuführen, die zum Lesen eines Werts von der Festplatte benötigt wird. Bei Festplatten mit kleinem Cache (4 MB Puffer) und Hybridlaufwerken mit wenigen GB Speicher kann es zu erheblichen Leistungsverbesserungen kommen, da die Datenlatenz für sequenzielles Lesen oder Lesen aus dem Hybridabschnitt möglicherweise um einige Größenordnungen schneller ist.

Ein Prozessorkern kann zwischen Threads wechseln (die Kosten für das Anhalten und Wiederaufnehmen eines Threads betragen etwa 100 Taktzyklen), während der erste Thread auf eine Eingabe mit hoher Latenz wartet (etwas teurer als Register (1 Takt) und RAM (5 Nanosekunden) )) Dazu gehören Festplatten-E/A, Netzwerkzugriff (Latenzzeit von 250 ms), Lesen von Daten von einer CD oder einem langsamen Bus oder ein Datenbankaufruf. Wenn Sie mehr Threads als Kerne haben, können Sie nützliche Aufgaben erledigen, während Aufgaben mit hoher Latenz gelöst werden.

Die CPU verfügt über einen Thread-Scheduler, der jedem Thread eine Priorität zuweist und einem Thread den Ruhezustand ermöglicht und nach einer vorbestimmten Zeit wieder aufgenommen wird. Es ist die Aufgabe des Thread-Schedulers, Thrash zu reduzieren, die auftreten würde, wenn jeder Thread nur 100 Anweisungen ausführte, bevor er wieder in den Ruhezustand versetzt wurde. Der Mehraufwand beim Wechseln von Threads würde den gesamten nützlichen Durchsatz des Prozessorkerns reduzieren.

Aus diesem Grund möchten Sie möglicherweise Ihr Problem in einer angemessenen Anzahl von Threads aufteilen. Wenn Sie Code schreiben, um eine Matrixmultiplikation durchzuführen, kann das Erstellen eines Threads pro Zelle in der Ausgabematrix übermäßig sein, während ein Thread pro Zeile oder pro n Zeilen in der Ausgabematrix den Aufwand für das Erstellen, Anhalten und Wiederaufnahme von Threads.

Dies ist auch der Grund, warum die Zweigvorhersage wichtig ist. Wenn Sie eine if-Anweisung haben, die einen Wert aus RAM laden muss, der Rumpf der if- und else-Anweisung jedoch Werte verwendet, die bereits in Register geladen wurden, kann der Prozessor einen oder beide Zweige ausführen, bevor die Bedingung ausgewertet wurde. Sobald die Bedingung wieder auftritt, wendet der Prozessor das Ergebnis der entsprechenden Verzweigung an und verwirft die andere. Das Ausführen potenziell nutzloser Arbeit ist hier wahrscheinlich besser als der Wechsel zu einem anderen Thread, was zu Thrashing führen kann.

Da wir uns von Single-Core-Prozessoren mit hoher Taktgeschwindigkeit zu Multi-Core-Prozessoren entwickelt haben, konzentrierte sich das Chip-Design darauf, mehr Kerne pro Chip zu zähmen, die gemeinsame Ressourcennutzung auf den Chips zwischen den Kernen zu verbessern, bessere Verzweigungsvorhersagealgorithmen, besseren Threadwechsel-Overhead und bessere Thread-Planung.

8
IceArdor

Ich stimme der Behauptung von @ kyoryu nicht zu, dass die ideale Zahl ein Thread pro CPU ist.

Stellen Sie es sich so vor: Warum haben wir Multi-Processing-Betriebssysteme? Für den Großteil der Computergeschichte hatten fast alle Computer eine CPU. Ab den 1960er Jahren verfügten alle "echten" Computer über Multi-Processing-Betriebssysteme (auch bekannt als Multitasking-Betriebssystem). 

Sie führen mehrere Programme aus, damit eines ausgeführt werden kann, während andere für Dinge wie E/A blockiert sind.

lassen Sie uns Argumente darüber aufstellen, ob Windows-Versionen vor NT Multitasking waren. Seitdem hatte jedes echte Betriebssystem ein Multitasking. Einige setzen es den Benutzern nicht aus, aber es ist sowieso vorhanden, es geht um das Radio des Mobiltelefons zu hören, mit dem GPS-Chip zu sprechen, die Mauseingabe zu akzeptieren usw.

Threads sind nur Aufgaben, die etwas effizienter sind. Es gibt keinen grundlegenden Unterschied zwischen einer Aufgabe, einem Prozess und einem Thread.

Eine CPU ist eine schreckliche Sache, die verschwendet werden muss. Halten Sie daher viele Dinge bereit, um sie einzusetzen, wenn Sie können.

Ich bin damit einverstanden, dass bei den meisten prozeduralen Sprachen, C, C++, Java usw. das Schreiben des richtigen sicheren Codes für Threads eine Menge Arbeit ist. Mit 6 Core-CPUs auf dem heutigen Markt und 16 Core-CPUs, die nicht weit entfernt sind, gehe ich davon aus, dass sich die Leute von diesen alten Sprachen entfernen werden, da Multithreading immer mehr zu einer kritischen Anforderung wird.

Meinungsverschiedenheiten mit @kyoryu sind nur meiner Meinung nach, der Rest ist Tatsache.

6
fishtoprecords

Zwar können Sie Threads je nach Hardware zur Beschleunigung von Berechnungen verwenden. Eine der Hauptanwendungen besteht jedoch darin, aus Gründen der Benutzerfreundlichkeit mehrere Aktionen gleichzeitig auszuführen.

Wenn Sie beispielsweise im Hintergrund etwas bearbeiten müssen und auch auf Eingaben der Benutzeroberfläche reagieren müssen, können Sie Threads verwenden. Ohne Threads hängt die Benutzeroberfläche jedes Mal, wenn Sie versuchen, eine umfangreiche Verarbeitung durchzuführen.

Siehe auch diese verwandte Frage: Praktische Verwendung für Threads

5
Cam

Stellen Sie sich einen Webserver vor, der eine beliebige Anzahl von Anforderungen bedienen muss. Sie müssen die Anforderungen parallel bearbeiten, da andernfalls jede neue Anforderung warten muss, bis alle anderen Anforderungen abgeschlossen sind (einschließlich Senden der Antwort über das Internet). In diesem Fall haben die meisten Webserver weit weniger Kerne als die Anzahl der Anfragen, die sie normalerweise bearbeiten.

Dies macht es auch für den Entwickler des Servers einfacher: Sie müssen nur ein Thread-Programm schreiben, das eine Anforderung bedient, und Sie müssen nicht daran denken, mehrere Anforderungen zu speichern, die Reihenfolge, in der Sie sie bedienen, usw.

5
tobiw

Die meisten Antworten beziehen sich auf Leistung und gleichzeitigen Betrieb. Ich werde das aus einem anderen Blickwinkel angehen.

Nehmen wir zum Beispiel ein vereinfachtes Terminal-Emulationsprogramm. Sie müssen folgende Dinge tun:

  • achten Sie auf eingehende Zeichen vom Remote-System und zeigen Sie diese an
  • achten Sie darauf, was von der Tastatur kommt und senden Sie sie an das Remote-System

(Echte Terminal-Emulatoren bieten mehr Möglichkeiten, einschließlich des potenziellen Echoes der eingegebenen Texte auf dem Display, aber wir werden das erstmal weitergeben.)

Nun ist die Schleife zum Lesen von der Fernbedienung gemäß dem folgenden Pseudocode einfach:

while get-character-from-remote:
    print-to-screen character

Die Schleife zum Überwachen der Tastatur und zum Senden ist ebenfalls einfach:

while get-character-from-keyboard:
    send-to-remote character

Das Problem ist jedoch, dass Sie dies gleichzeitig tun müssen. Der Code muss jetzt mehr wie folgt aussehen, wenn Sie kein Threading haben:

loop:
    check-for-remote-character
    if remote-character-is-ready:
        print-to-screen character
    check-for-keyboard-entry
    if keyboard-is-ready:
        send-to-remote character

Die Logik, auch in diesem absichtlich vereinfachten Beispiel, das die Komplexität der Kommunikation in der realen Welt nicht berücksichtigt, ist ziemlich verschleiert. Beim Threading können jedoch auch auf einem einzelnen Kern die beiden Pseudocode-Schleifen unabhängig voneinander existieren, ohne ihre Logik zu vernetzen. Da beide Threads meistens E/A-gebunden sind, belasten sie die CPU nicht, obwohl sie streng genommen CPU-Ressourcen verschwenden, als dies bei der integrierten Schleife der Fall wäre.

Natürlich ist die Verwendung in der realen Welt komplizierter als die oben genannten. Die Komplexität der integrierten Schleife steigt jedoch exponentiell an, wenn Sie der Anwendung weitere Bedenken hinzufügen. Die Logik wird immer fragmentierter und Sie müssen mit Techniken wie Zustandsmaschinen, Coroutinen usw. beginnen, um die Dinge handhabbar zu machen. Überschaubar, aber nicht lesbar. Durch Einfädeln bleibt der Code lesbarer.

Warum sollten Sie kein Threading verwenden?

Wenn Ihre Aufgaben CPU-gebunden und nicht E/A-gebunden sind, verlangsamt das Threading tatsächlich Ihr System. Leistung wird leiden. In vielen Fällen viel. ("Thrashing" ist ein häufiges Problem, wenn Sie zu viele CPU-gebundene Threads ablegen. Sie müssen mehr Zeit damit verbringen, die aktiven Threads zu ändern, als den Inhalt der Threads selbst auszuführen.) Dies ist auch einer der Gründe für die oben genannte Logik So einfach ist, dass ich ganz bewusst ein simples (und unrealistisches) Beispiel gewählt habe. Wenn Sie das, was auf dem Bildschirm eingegeben wurde, wiederholen möchten, haben Sie eine neue Welt der Verletzungen, wenn Sie das Sperren gemeinsam genutzter Ressourcen einführen. Mit nur einer gemeinsam genutzten Ressource ist dies nicht so sehr ein Problem, aber es wird immer größer, je mehr Ressourcen Sie zur Verfügung haben.

Am Ende geht es beim Threading also um viele Dinge. Zum Beispiel geht es darum, E/A-gebundene Prozesse reaktionsfähiger zu gestalten (auch wenn sie insgesamt weniger effizient sind), wie einige bereits gesagt haben. Es geht auch darum, die Logik einfacher zu machen (aber nur, wenn Sie den gemeinsamen Status minimieren). Es geht um eine Menge Sachen, und Sie müssen entscheiden, ob ihre Vorteile von Fall zu Fall die Nachteile überwiegen.

Viele Threads schlafen und warten auf Benutzereingaben, E/A und andere Ereignisse.

3
Puppy

Threads können bei der Reaktionsfähigkeit in UI-Anwendungen helfen. Darüber hinaus können Sie Threads verwenden, um Ihren Cores mehr Arbeit zu ermöglichen. Auf einem einzelnen Kern können Sie beispielsweise einen Thread IO und einen anderen etwas berechnen lassen. Wenn es sich um einen Single-Thread handelt, könnte der Kern im Wesentlichen inaktiv sein und warten, bis IO abgeschlossen ist. Dies ist ein ziemlich hohes Beispiel, aber Threads können definitiv verwendet werden, um Ihre CPU etwas härter zu schlagen.

2
Anon

Ein Prozessor oder eine CPU ist der physikalische Chip, der in das System eingesteckt ist. Ein Prozessor kann mehrere Kerne haben (ein Kern ist der Teil des Chips, der Anweisungen ausführen kann). Ein Kern kann dem Betriebssystem als mehrere virtuelle Prozessoren erscheinen, wenn er gleichzeitig mehrere Threads ausführen kann (ein Thread ist eine einzelne Befehlsfolge).

Ein Prozess ist ein anderer Name für eine Anwendung. Prozesse sind im Allgemeinen unabhängig voneinander. Wenn ein Prozess stirbt, führt dies nicht dazu, dass auch ein anderer Prozess stirbt. Es ist möglich, dass Prozesse kommunizieren oder Ressourcen wie Speicher oder E/A gemeinsam nutzen.

Jeder Prozess verfügt über einen separaten Adressraum und einen separaten Stapel. Ein Prozess kann mehrere Threads enthalten, die jeweils Anweisungen gleichzeitig ausführen können. Alle Threads in einem Prozess haben denselben Adressraum, aber jeder Thread hat einen eigenen Stapel.

Hoffentlich helfen diese Definitionen und die weitere Erforschung dieser Grundlagen zum besseren Verständnis.

2
Srikar Doddi

Wie einige APIs entworfen wurden, haben Sie keine Wahl , sie müssen jedoch in einem separaten Thread ausgeführt werden (alles mit blockierenden Operationen). Ein Beispiel wäre Pythons HTTP-Bibliotheken (AFAIK).

Normalerweise ist dies jedoch kein großes Problem (wenn es sich um ein Problem handelt, sollte das Betriebssystem oder die API mit einem alternativen asynchronen Betriebsmodus geliefert werden, z. B. select(2)), da dies wahrscheinlich bedeutet, dass der Thread während des Wartens auf I schläft/O Fertigstellung. Auf der anderen Seite, wenn etwas eine schwere Berechnung durchführt, müssen Sie es in einen anderen Thread als den GUI-Thread einfügen (es sei denn, Sie genießen das manuelle Multiplexing).

Die ideale Verwendung von Threads ist in der Tat einer pro Kern.

Wenn Sie jedoch nicht ausschließlich asynchrone/nicht blockierende E/A verwenden, besteht eine gute Chance, dass Sie zu einem bestimmten Zeitpunkt Threads für IO blockieren, wodurch Ihre CPU nicht verwendet wird.

Typische Programmiersprachen erschweren außerdem die Verwendung von 1 Thread pro CPU. Sprachen, die auf Parallelität ausgelegt sind (z. B. Erlang), können die Verwendung zusätzlicher Threads erleichtern.

1
kyoryu

Ich weiß, dass dies eine sehr alte Frage mit vielen guten Antworten ist, aber ich möchte hier etwas aufzeigen, das in der aktuellen Umgebung wichtig ist:

Wenn Sie eine Anwendung für Multithreading entwerfen möchten, sollten Sie nicht für eine bestimmte Hardwareeinstellung entwerfen. Die CPU-Technologie hat sich seit Jahren ziemlich schnell weiterentwickelt und die Anzahl der Kernkomponenten steigt stetig. Wenn Sie Ihre Anwendung absichtlich so entwerfen, dass nur 4 Threads verwendet werden, schränken Sie sich möglicherweise in einem Octa-Core-System ein (z. B.). Mittlerweile sind sogar 20-Core-Systeme im Handel erhältlich, sodass ein solches Design definitiv mehr schadet als nützt.

0
Jai

Als Antwort auf Ihre erste Vermutung: Multi-Core-Maschinen können mehrere Prozesse gleichzeitig ausführen, nicht nur die mehreren Threads eines einzelnen Prozesses.

Als Antwort auf Ihre erste Frage: In der Regel ist es das Ziel mehrerer Threads, gleichzeitig mehrere Aufgaben in einer Anwendung auszuführen. Die klassischen Beispiele im Internet sind ein E-Mail-Programm, das E-Mails sendet und empfängt, und ein Webserver, der Seitenanforderungen empfängt und sendet. (Beachten Sie, dass es im Wesentlichen unmöglich ist, ein System wie Windows darauf zu beschränken, nur einen Thread oder sogar nur einen Prozess auszuführen.) Wenn Sie den Windows Task-Manager ausführen, wird normalerweise eine lange Liste aktiver Prozesse angezeigt, von denen viele mehrere Threads ausführen. )

Zur zweiten Frage: Die meisten Prozesse/Threads sind nicht an die CPU gebunden (dh sie laufen nicht kontinuierlich und ununterbrochen), sondern halten an und warten häufig, bis die E/A abgeschlossen ist. Während dieser Wartezeit können andere Prozesse/Threads ausgeführt werden, ohne vom wartenden Code "gestohlen" zu werden (selbst auf einer einzelnen Kernmaschine).

0
joe snyder