Ich dachte, der Sinn eines Multi-Core-Computers ist, dass er mehrere Threads gleichzeitig ausführen kann. Wenn Sie in diesem Fall eine Quad-Core-Maschine haben, was bringt es, wenn mehr als 4 Threads gleichzeitig ausgeführt werden? Würden sie sich nicht einfach Zeit (CPU-Ressourcen) stehlen?
multithreading
hardware
cpu-cores
Nick Heiner
quelle
quelle
Antworten:
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 ausführen, hat jeder Prozess mindestens einen Thread und viele mehr. Alle diese Prozesse werden gleichzeitig ausgeführt. Wahrscheinlich laufen gerade mehrere hundert Threads auf Ihrem Computer. Sie werden nie eine Situation bekommen, in der ein Thread läuft, ohne dass ihm Zeit "gestohlen" wird. (Nun, Sie könnten, wenn es in Echtzeit läuft , wenn Sie ein Echtzeit-Betriebssystem verwenden oder sogar unter Windows,
Vor diesem Hintergrund lautet die Antwort: Ja, mehr als vier Threads auf einem echten Vierkerncomputer können dazu führen, dass sie sich gegenseitig Zeit stehlen, jedoch nur, wenn jeder einzelne Thread 100% CPU benötigt . Wenn ein Thread nicht zu 100% funktioniert (wie es ein UI-Thread möglicherweise nicht tut oder ein Thread eine kleine Menge Arbeit erledigt oder auf etwas anderes wartet), ist ein anderer Thread, der geplant wird, tatsächlich eine gute Situation.
Es ist tatsächlich komplizierter als das:
Was ist, wenn Sie fünf Arbeiten haben, die alle gleichzeitig erledigt werden müssen? Es ist sinnvoller, sie alle gleichzeitig auszuführen, als vier davon auszuführen und dann die fünfte später auszuführen.
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 möglicherweise zu Wartezeiten kommen, die nichts Nützliches bewirken. Dies ist eine sehr häufige Situation.
Wenn Sie Arbeiten ausführen müssen, besteht ein gängiger Mechanismus darin, einen Threadpool zu verwenden. Es mag sinnvoll erscheinen, die gleiche Anzahl von Threads wie Kerne zu haben, aber der .Net-Threadpool verfügt über bis zu 250 Threads pro Prozessor . Ich bin nicht sicher, warum sie dies tun, aber meine Vermutung hängt mit der Größe der Aufgaben zusammen, die zum Ausführen auf den Threads gegeben 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 ausführen, bei der es sich möglicherweise nicht um CPU handelt -gebunden. Ermitteln Sie anhand der Profilerstellung und Messung die Anzahl der benötigten Threads. 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 keine Sorgen, es sei denn, Ihr Programm ist wirklich leistungskritisch :)
quelle
Nur weil ein Thread existiert, heißt das nicht immer, dass er aktiv läuft. Bei vielen Thread-Anwendungen werden einige der Threads in den Ruhezustand versetzt, bis sie etwas tun müssen. Beispielsweise können Benutzereingaben dazu führen, dass Threads aufgeweckt, verarbeitet und wieder in den Ruhezustand versetzt werden.
Threads sind im Wesentlichen einzelne Aufgaben, die unabhängig voneinander ausgeführt werden können, ohne dass der Fortschritt einer anderen Aufgabe bekannt sein 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 in der Schlange stehen müssen.
quelle
Der Punkt ist, dass Sie, obwohl Sie keine echte Beschleunigung erhalten, wenn die Threadanzahl die Kernanzahl überschreitet, Threads verwenden können, um logische Teile zu entwirren, die nicht voneinander abhängig sein müssen.
Selbst in einer mäßig komplexen Anwendung versucht die Verwendung eines einzelnen Threads, alles schnell zu erledigen, den "Fluss" Ihres Codes zu beeinträchtigen. Der einzelne Thread verbringt die meiste Zeit damit, dies abzufragen, dies zu überprüfen und Routinen bedingt nach Bedarf aufzurufen, und es wird schwierig, etwas anderes als einen Morast von Kleinigkeiten zu erkennen.
Vergleichen Sie dies mit dem Fall, in dem Sie Threads Aufgaben zuweisen können, sodass Sie anhand eines einzelnen Threads sehen können, was dieser Thread tut. Beispielsweise 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, an einen anderen Arbeitsthread weiterleiten. Der Worker-Thread kann Eingaben aus einer Reihe anderer Quellen bearbeiten. Der Code für jedes dieser Elemente weist einen sauberen, zielgerichteten Ablauf auf, ohne explizit überprüfen zu müssen, ob nichts anderes zu tun ist.
Wenn Sie die Arbeit auf diese Weise partitionieren, kann sich Ihre Anwendung darauf verlassen, dass das Betriebssystem plant, was als Nächstes mit der CPU zu tun ist, sodass Sie nicht überall in Ihrer Anwendung explizite bedingte Überprüfungen durchführen müssen, was möglicherweise blockiert und was verarbeitet werden kann.
quelle
Wenn ein Thread auf eine Ressource wartet (z. B. Laden eines Werts aus dem RAM in ein Register, Festplatten-E / A, Netzwerkzugriff, Starten eines neuen Prozesses, Abfragen einer Datenbank oder Warten auf Benutzereingaben), kann der Prozessor an einer arbeiten einen anderen Thread und kehren Sie zum ersten Thread zurück, sobald die Ressource verfügbar ist. Dies reduziert die Zeit, die die CPU im Leerlauf verbringt, da die CPU Millionen von Vorgängen ausführen kann, anstatt im Leerlauf zu sitzen.
Stellen Sie sich einen Thread vor, der Daten von einer Festplatte lesen muss. Im Jahr 2014 arbeitet ein typischer Prozessorkern mit 2,5 GHz und kann möglicherweise 4 Befehle pro Zyklus ausführen. Mit einer Zykluszeit von 0,4 ns kann der Prozessor 10 Befehle pro Nanosekunde ausführen. Bei typischen Suchzeiten für mechanische Festplatten von etwa 10 Millisekunden kann der Prozessor 100 Millionen Anweisungen in der Zeit ausfü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 können erhebliche Leistungsverbesserungen auftreten, da die Datenlatenz für sequentielle Lesevorgänge oder Lesevorgänge aus dem Hybridabschnitt um mehrere Größenordnungen schneller sein kann.
Ein Prozessorkern kann zwischen Threads wechseln (die Kosten für das Anhalten und Fortsetzen eines Threads betragen etwa 100 Taktzyklen), während der erste Thread auf eine Eingabe mit hoher Latenz wartet (alles, was teurer ist als Register (1 Takt) und RAM (5 Nanosekunden)) Festplatten-E / A, Netzwerkzugriff (Latenz von 250 ms), Lesen von Daten von einer CD oder einem langsamen Bus oder ein Datenbankaufruf. Wenn mehr Threads als Kerne vorhanden sind, kann nützliche Arbeit geleistet werden, während Aufgaben mit hoher Latenz gelöst werden.
Die CPU verfügt über einen Thread-Scheduler, der jedem Thread Priorität zuweist und es einem Thread ermöglicht, in den Ruhezustand zu wechseln und nach einer festgelegten Zeit fortzufahren. Es ist die Aufgabe des Thread-Schedulers, das Thrashing zu reduzieren, was auftreten würde, wenn jeder Thread nur 100 Anweisungen ausführen würde, bevor er wieder in den Ruhezustand versetzt wird. Der Overhead beim Wechseln von Threads würde den gesamten nützlichen Durchsatz des Prozessorkerns verringern.
Aus diesem Grund möchten Sie Ihr Problem möglicherweise in eine angemessene Anzahl von Threads aufteilen. Wenn Sie Code zur Durchführung der Matrixmultiplikation geschrieben haben, ist das Erstellen eines Threads pro Zelle in der Ausgabematrix möglicherweise übermäßig, während ein Thread pro Zeile oder pro n vorhanden ist Zeilen in der Ausgabematrix die Overhead-Kosten für das Erstellen, Anhalten und Fortsetzen von Threads verringern kann.
Dies ist auch der Grund, warum die Verzweigungsvorhersage wichtig ist. Wenn Sie eine if-Anweisung haben, die das Laden eines Werts aus dem RAM erfordert, der Hauptteil der if- und else-Anweisungen 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 zurückkehrt, wendet der Prozessor das Ergebnis der entsprechenden Verzweigung an und verwirft die andere. Hier möglicherweise nutzlose Arbeiten auszuführen ist wahrscheinlich besser als zu einem anderen Thread zu wechseln, was zu Thrashing führen kann.
Auf dem Weg von Single-Core-Prozessoren mit hoher Taktrate zu Multi-Core-Prozessoren hat sich das Chip-Design darauf konzentriert, mehr Kerne pro Chip zu stopfen, die gemeinsame Nutzung von On-Chip-Ressourcen zwischen Kernen zu verbessern, Algorithmen für die Verzweigungsvorhersage zu verbessern und den Overhead für das Umschalten von Threads zu verbessern. und bessere Thread-Planung.
quelle
Die meisten der obigen Antworten beziehen sich auf Leistung und gleichzeitigen Betrieb. Ich werde dies aus einem anderen Blickwinkel betrachten.
Nehmen wir zum Beispiel ein vereinfachtes Terminalemulationsprogramm. Sie müssen folgende Dinge tun:
(Echte Terminalemulatoren leisten mehr, einschließlich des potenziellen Echo der von Ihnen eingegebenen Daten auf dem Display, aber wir werden dies vorerst weitergeben.)
Jetzt ist die Schleife zum Lesen von der Fernbedienung gemäß dem folgenden Pseudocode einfach:
Die Schleife zum Überwachen der Tastatur und zum Senden ist ebenfalls einfach:
Das Problem ist jedoch, dass Sie dies gleichzeitig tun müssen. Der Code muss jetzt mehr so aussehen, wenn Sie kein Threading haben:
Die Logik ist selbst in diesem bewusst vereinfachten Beispiel, das die reale Komplexität der Kommunikation nicht berücksichtigt, ziemlich verschleiert. Beim Threading können die beiden Pseudocode-Schleifen jedoch auch auf einem einzelnen Kern unabhängig voneinander existieren, ohne ihre Logik zu verschachteln. Da beide Threads größtenteils E / A-gebunden sind, wird die CPU nicht stark belastet, obwohl sie streng genommen mehr CPU-Ressourcen verschwenden als die integrierte Schleife.
Jetzt ist die Verwendung in der realen Welt natürlich komplizierter als oben beschrieben. 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 Techniken wie Zustandsautomaten, Coroutinen usw. verwenden, um die Dinge handhabbar zu machen. Überschaubar, aber nicht lesbar. Durch Threading bleibt der Code besser lesbar.
Warum sollten Sie kein Threading verwenden?
Wenn Ihre Aufgaben CPU-gebunden statt E / A-gebunden sind, verlangsamt Threading Ihr System tatsächlich. Die Leistung wird leiden. In vielen Fällen viel. ("Thrashing" ist ein häufiges Problem, wenn Sie zu viele CPU-gebundene Threads löschen. Sie verbringen mehr Zeit damit, die aktiven Threads zu ändern, als den Inhalt der Threads selbst auszuführen.) Einer der Gründe ist die obige Logik So einfach ist, dass ich ganz bewusst ein vereinfachtes (und unrealistisches) Beispiel gewählt habe. Wenn Sie die Eingabe auf dem Bildschirm wiederholen möchten, haben Sie eine neue Welt voller Verletzungen, wenn Sie die Sperrung 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 gemeinsam nutzen können.
Am Ende geht es beim Threading also um viele Dinge. Zum Beispiel geht es darum, E / A-gebundene Prozesse reaktionsfähiger zu machen (auch wenn sie insgesamt weniger effizient sind), wie einige bereits gesagt haben. Es geht auch darum, die Logik einfacher zu befolgen (aber nur, wenn Sie den gemeinsamen Status minimieren). Es geht um eine Menge Dinge, und Sie müssen von Fall zu Fall entscheiden, ob die Vorteile die Nachteile überwiegen.
quelle
Obwohl Sie Threads verwenden können, um Berechnungen abhängig von Ihrer Hardware zu beschleunigen, besteht eine ihrer Hauptanwendungen darin, aus Gründen der Benutzerfreundlichkeit mehr als eine Sache gleichzeitig zu tun.
Wenn Sie beispielsweise im Hintergrund etwas verarbeiten müssen und auch weiterhin auf Eingaben in die Benutzeroberfläche reagieren müssen, können Sie Threads verwenden. Ohne Threads würde die Benutzeroberfläche jedes Mal hängen bleiben, wenn Sie versuchen, eine schwere Verarbeitung durchzuführen.
Siehe auch diese verwandte Frage: Praktische Verwendung für Threads
quelle
Ich bin mit der Behauptung von @ kyoryu, dass die ideale Anzahl ein Thread pro CPU ist, überhaupt nicht einverstanden.
Stellen Sie sich das so vor: Warum haben wir Multi-Processing-Betriebssysteme? Während des größten Teils der Computergeschichte hatten fast alle Computer eine CPU. Ab den 1960er Jahren verfügten alle "echten" Computer über Multi-Processing-Betriebssysteme (auch bekannt als Multi-Tasking).
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 beiseite legen, ob Windows-Versionen vor NT Multitasking waren. Seitdem hatte jedes echte Betriebssystem Multitasking. Einige setzen es nicht Benutzern aus, aber es ist trotzdem da, um beispielsweise das Radio des Mobiltelefons zu hören, mit dem GPS-Chip zu sprechen, Mauseingaben 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 man verschwenden muss. Halten Sie also viele Dinge bereit, um sie zu verwenden, wenn Sie können.
Ich werde zustimmen, dass mit den meisten prozeduralen Sprachen, C, C ++, Java usw., das Schreiben von richtigem thread-sicherem Code eine Menge Arbeit ist. Mit 6 Kern-CPUs auf dem heutigen Markt und 16 Kern-CPUs in der Nähe erwarte ich, dass sich die Leute von diesen alten Sprachen entfernen werden, da Multithreading immer wichtiger wird.
Meinungsverschiedenheiten mit @kyoryu sind nur IMHO, der Rest ist Tatsache.
quelle
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 des Sendens der Antwort über das Internet). In diesem Fall haben die meisten Webserver weit weniger Kerne als die Anzahl der Anforderungen, die sie normalerweise bedienen.
Dies erleichtert es auch dem Entwickler des Servers: Sie müssen nur ein Thread-Programm schreiben, das eine Anfrage bedient, Sie müssen nicht über das Speichern mehrerer Anfragen, die Reihenfolge, in der Sie sie bedienen, usw. nachdenken.
quelle
Viele Threads schlafen und warten auf Benutzereingaben, E / A und andere Ereignisse.
quelle
Threads können die Reaktionsfähigkeit in UI-Anwendungen verbessern. Darüber hinaus können Sie Threads verwenden, um mehr Arbeit aus Ihren Kernen herauszuholen. Auf einem einzelnen Kern kann beispielsweise ein Thread E / A ausführen und ein anderer einige Berechnungen durchführen. Wenn es sich um einen Single-Thread handelt, könnte der Kern im Wesentlichen inaktiv sein und auf den Abschluss der E / A warten. Das ist ein ziemlich gutes Beispiel, aber Threads können definitiv verwendet werden, um Ihre CPU ein bisschen härter zu schlagen.
quelle
Ein Prozessor oder eine CPU ist der physische Chip, der an das System angeschlossen 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 mehrere Threads gleichzeitig ausführen kann (ein Thread ist eine einzelne Folge von Anweisungen).
Ein Prozess ist ein anderer Name für eine Anwendung. Im Allgemeinen sind Prozesse unabhängig voneinander. Wenn ein Prozess stirbt, stirbt auch kein anderer Prozess. Es ist möglich, dass Prozesse kommunizieren oder Ressourcen wie Speicher oder E / A gemeinsam nutzen.
Jeder Prozess hat einen separaten Adressraum und Stapel. Ein Prozess kann mehrere Threads enthalten, die jeweils Anweisungen gleichzeitig ausführen können. Alle Threads in einem Prozess teilen sich den gleichen Adressraum, aber jeder Thread hat seinen eigenen Stapel.
Hoffentlich helfen Ihnen diese Definitionen und weitere Untersuchungen unter Verwendung dieser Grundlagen beim Verständnis.
quelle
Die ideale Verwendung von Threads ist in der Tat eine pro Kern.
Wenn Sie jedoch nicht ausschließlich asynchrone / nicht blockierende E / A verwenden, besteht eine gute Chance, dass irgendwann Threads auf E / A blockiert werden, die Ihre CPU nicht verwenden.
Außerdem erschweren typische Programmiersprachen die Verwendung von 1 Thread pro CPU. Sprachen, die auf Parallelität ausgelegt sind (z. B. Erlang), können es einfacher machen, keine zusätzlichen Threads zu verwenden.
quelle
Bei der Art und Weise, wie einige APIs entworfen wurden, haben Sie keine andere Wahl , als sie in einem separaten Thread auszuführen (alles mit Blockierungsvorgängen). Ein Beispiel wären die HTTP-Bibliotheken (AFAIK) von Python.
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 ausgeliefert werden, z. B. :)
select(2)
, da dies wahrscheinlich bedeutet, dass der Thread während des Wartens auf E / A in den Ruhezustand versetzt wird O Abschluss. Auf der anderen Seite, wenn etwas eine schwere Berechnung tut, Sie haben , um es in einem separaten Thread als sagen sie gesagt, der GUI - Thread (es sei denn , Sie genießen manuelles Multiplexing).quelle
Ich weiß, dass dies eine super alte Frage mit vielen guten Antworten ist, aber ich bin hier, um auf etwas hinzuweisen, das in der gegenwärtigen 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 schreitet seit Jahren recht schnell voran und die Anzahl der Kerne nimmt stetig zu. Wenn Sie Ihre Anwendung absichtlich so gestalten, dass nur 4 Threads verwendet werden, beschränken Sie sich möglicherweise (z. B.) auf ein Octa-Core-System. Jetzt sind sogar 20-Kern-Systeme im Handel erhältlich, sodass ein solches Design definitiv mehr schadet als nützt.
quelle
Als Antwort auf Ihre erste Vermutung: Multi-Core-Maschinen können gleichzeitig mehrere Prozesse ausführen, nicht nur die mehreren Threads eines einzelnen Prozesses.
Antwort auf Ihre erste Frage: Bei mehreren Threads geht es normalerweise darum, mehrere Aufgaben gleichzeitig 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 auf die Ausführung nur eines Threads oder sogar nur eines Prozesses zu reduzieren. Wenn Sie den Windows Task-Manager ausführen, wird normalerweise eine lange Liste aktiver Prozesse angezeigt, von denen viele mehrere Threads ausführen. )
Antwort auf Ihre zweite Frage: Die meisten Prozesse / Threads sind nicht CPU-gebunden (dh sie werden nicht kontinuierlich und ununterbrochen ausgeführt), 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 den wartenden Code zu "stehlen" (selbst auf einem Single-Core-Computer).
quelle
Ein Thread ist eine Abstraktion, mit der Sie Code schreiben können, der so einfach wie eine Abfolge von Operationen ist, ohne zu wissen, dass der Code mit anderen Codes verschachtelt ausgeführt wird oder auf E / A geparkt oder (möglicherweise etwas bewusster) auf andere Threads gewartet wird Ereignisse oder Nachrichten.
quelle
Der Punkt ist, dass die überwiegende Mehrheit der Programmierer nicht versteht, wie man eine Zustandsmaschine entwirft. Wenn der Programmierer in der Lage ist, alles in einen eigenen Thread zu stellen, muss er nicht mehr darüber nachdenken, wie er den Status verschiedener laufender Berechnungen effizient darstellen kann, damit sie unterbrochen und später wieder aufgenommen werden können.
Betrachten Sie als Beispiel die Videokomprimierung, eine sehr CPU-intensive Aufgabe. Wenn Sie ein GUI-Tool verwenden, möchten Sie wahrscheinlich, dass die Benutzeroberfläche weiterhin reagiert (Fortschritt anzeigen, auf Abbruchanforderungen reagieren, Fenstergröße ändern usw.). Sie entwerfen Ihre Encoder-Software so, dass eine große Einheit (ein oder mehrere Frames) gleichzeitig verarbeitet und in einem eigenen Thread ausgeführt wird, der von der Benutzeroberfläche getrennt ist.
Sobald Sie feststellen, dass es schön gewesen wäre, den laufenden Codierungsstatus zu speichern, damit Sie das Programm schließen können, um einen Neustart durchzuführen oder ein ressourcenhungriges Spiel zu spielen, sollten Sie natürlich gelernt haben, wie man Zustandsautomaten aus dem Anfang. Entweder das, oder Sie entscheiden sich für ein völlig neues Problem des Ruhezustands Ihres Betriebssystems, damit Sie einzelne Apps anhalten und auf der Festplatte wieder aufnehmen können ...
quelle