Was macht die ordnungsgemäße Verwendung von Threads bei der Programmierung aus?

13

Ich habe es satt zu hören, dass Leute empfehlen, nur einen Thread pro Prozessor zu verwenden, während viele Programme bis zu 100 pro Prozess verwenden! nehmen Sie zum Beispiel einige gängige Programme

vb.net ide uses about 25 thread when not debugging
System uses about 100
chrome uses about 19
Avira uses more than about 50

Jedes Mal, wenn ich eine Thread-bezogene Frage stelle, werde ich fast jedes Mal daran erinnert, dass ich nicht mehr als einen Thread pro Prozessor verwenden sollte und alle oben genannten Programme mit einem einzigen Prozessor mein System ruinieren.

Schmied
quelle
7
Diese Empfehlung ist zu weit gefasst. Das Limit von einem Thread pro Prozessor ist nur für rechnergebundene Anwendungen geeignet. Die meisten Programme sind an E / A gebunden, unabhängig davon, ob es sich um Netzwerkverkehr, Festplattenzugriff oder sogar RAM handelt. Aus diesem Grund haben Webserver, Datenbanken usw. Thread-Pools mit viel mehr Threads als Prozessorkerne.
Kilian Foth
2
"Ich werde fast jedes Mal daran erinnert, dass ich nicht mehr als einen Thread pro Prozessor verwenden sollte"? Können Sie Links oder Beispiele posten? Fast jedes Mal?
S.Lott
2
"... Leute empfehlen, dass Sie nur einen Thread pro Prozess verwenden." Wer sind diese Leute? Die Planung hat sich seit dem Mittelalter erheblich verbessert.
Rein Henrichs
2
Sie sollten nicht mehr als einen UI-Thread pro Prozess haben.
SLaks
3
@ Billy ONeal, Ihre Bearbeitung machte die Frage bedeutungslos
SK-Logik

Antworten:

22

Sie sollten nur einen Thread pro Prozessor verwenden,

Möglicherweise in HPC, wo Sie maximale Effizienz wollen - aber sonst das Dümmste, was ich heute gehört habe!

Sie sollten die Anzahl der Threads verwenden, die für das Design des Programms geeignet sind und dennoch eine akzeptable Leistung bieten.

Für einen Webserver kann es sinnvoll sein, für jede eingehende Verbindung einen Thread auszulösen (obwohl es für sehr stark ausgelastete Server bessere Möglichkeiten gibt).

Für eine Idee ist es nicht unangemessen, dass jedes Tool in einem eigenen Thread ausgeführt wird. Ich vermute, dass viele der Threads, die für die .NET-IDE gemeldet wurden, Dinge wie Protokollierung und E / A-Aufgaben sind, die in ihren eigenen Threads gestartet werden, damit sie weiterhin nicht blockiert werden können.

Martin Beckett
quelle
9
Jetzt habe ich mich gefragt, was das Dümmste ist, was Sie jemals gehört haben!
Michael K
3
@Michael - Ich habe Studenten unterrichtet und an Rüstungsverträgen gearbeitet - Sie würden nicht glauben, was Dummes ich gehört habe!
Martin Beckett
1
Haben wir sie auf TheDailyWTF.com gesehen?
FrustratedWithFormsDesigner
Ich kann sie jetzt nicht wirklich finden, aber sehen Sie sich diesen Link an social.msdn.microsoft.com/Forums/en-US/vbgeneral/thread/…
Smith
2
Haben höchstens eine CPU-gebundene Thread pro Prozessor der Anwendung zugeordnet. E / A-gebundene Threads sind kein großes Problem (außer dem Speicher, den sie belegen), und es ist wichtig zu bedenken, dass Apps darauf beschränkt werden können, nur eine Teilmenge der System-CPUs zu verwenden. Schließlich ist es (normalerweise) der Computer des Benutzers / Administrators und nicht der des Programmierers.
Donal Fellows
2

Der Hinweis "Ein Thread pro Kern" gilt, wenn der Zweck die Geschwindigkeit durch parallele Ausführung ist.

Ein völlig anderer und ebenso gültiger Grund ist die Einfachheit des Codes, wenn er auf unvorhersehbare Ereignisse reagieren muss. Wenn ein Programm also 100 Sockets abhören muss und jedem seine volle Aufmerksamkeit zu widmen scheint, ist dies eine perfekte Verwendung für das Threading. Ein weiteres Beispiel ist eine Benutzeroberfläche, in der ein Thread Benutzeroberflächenereignisse verarbeitet, während ein anderer die Hintergrundverarbeitung ausführt.

Mike Dunlavey
quelle
1
Die E / A-gebundene Verarbeitung kann als ein Thread pro Ereignisquelle ausgeführt werden, oder es können mehrere Ereignisquellen auf einen einzelnen Thread gemultiplext werden. Multiplexed Code ist normalerweise sowohl komplexer als auch effizienter.
Donal Fellows
2

Sie möchten einen Thread für jede Berechnung, der mit anderen Raten als andere Berechnungen ablaufen kann.

Für parallele CPU-gebundene Berechnungen, die in großen Arbeitsblöcken ausgeführt werden, benötigen Sie im Allgemeinen einen Thread pro CPU, da mehr Threads nicht helfen und nur einen Scheduler-Overhead verursachen, wenn sie alle ausgelastet sind. Wenn die Arbeitsblöcke zeitlich unregelmäßig groß sind oder zur Laufzeit dynamisch generiert werden (häufig bei der Verarbeitung großer komplexer Datenstrukturen), möchten Sie diese Blöcke möglicherweise an viele Threads anhängen, sodass ein Scheduler immer über große Datenmengen verfügt Legen Sie fest, ob nach Abschluss eines Arbeitsblocks alle CPUs ausgelastet sein sollen.

Für E / A-gebundene Berechnungen benötigen Sie im Allgemeinen einen Thread für jeden unabhängigen E / A-"Kanal", da diese mit unterschiedlichen Raten kommunizieren und Threads, die auf dem Kanal blockiert sind, andere Threads nicht daran hindern, Fortschritte zu erzielen.

Ira Baxter
quelle
Beachten Sie jedoch, dass dieser Threading-Stil zu seltsam gestalteten Programmen führen kann. Ich habe ein 4-Thread-Programm gesehen, das einen Thread zum Lesen von Datensätzen aus einer DB-Tabelle, einen Thread zum Schreiben von transformierten Datensätzen in einen Socket und einen Thread zum Lesen der Antworten auf diese Socket-Schreibvorgänge (die wieder außer Betrieb waren) hatte und asynchron) und einen Thread zum Ändern des ursprünglichen DB-Datensatzes mit der Antwort. Unintuitive Fehlerbedingungen traten auf.
Bruce Ediger
Eine Ansicht ist, dass dieser Stil ungerade Programme erzeugt. Eine andere Ansicht ist, dass dies der natürliche Stil ist, den die Programme hätten haben sollen. Keine Ahnung von den "nicht intuitiven" Fehlerbedingungen; Wenn viele Dinge passieren und einer von ihnen einen Fehler anzeigt, ist es für viele Sprachen ein Problem, sicherzustellen, dass er ordnungsgemäß über die asynchronen Berechnungen verteilt wird [dummerweise sind Java-Ausnahmen nicht an Thread-Grenzen definiert], aber nicht ein Problem mit dem Programmstil. (Unsere PARLANSE-Programmiersprache behandelt Ausnahmen über Threadgrenzen hinweg sauber, so dass es möglich ist, dies richtig zu machen.)
Ira Baxter
1

Die Faustregel für Threads lautet: Sie möchten, dass mindestens ein "aktiver" Worker-Thread für jede auf dem Computer verfügbare "Ausführungseinheit" auf dem Computer vorhanden ist (die Befehle können sofort ausgeführt werden, wenn die CPU-Zeit abgelaufen ist). Eine "Ausführungseinheit" ist ein logischer Befehlsprozessor, sodass ein Quad-Chip-Quad-Core-Xeon-Hyperthread-Server 32 EUs (4 Chips, 4 Kerne pro Chip, jeder Hyperthread) aufweist. Ihr durchschnittlicher Core i7 hätte 8.

Ein Thread pro EU nutzt die CPU-Leistung voll aus, vorausgesetzt, die Threads sind immer in Betrieb. Dies ist fast nie der Fall, da Threads Zugriff auf nicht zwischengespeicherten Speicher, die Festplatte, Netzwerkports usw. benötigen, auf die sie warten müssen, und für deren Ausführung keine aktive CPU-Aufmerksamkeit erforderlich ist. Auf diese Weise können Sie die Gesamteffizienz weiter steigern, wenn sich mehr Threads in der Warteschlange befinden und nur noch wenige verfügbar sind. Dies ist mit Kosten verbunden. Wenn eine CPU einen Thread wechselt, muss sie die Register, den Ausführungszeiger und andere Statusinformationen des Threads zwischenspeichern, die normalerweise im Innersten einer EU gespeichert sind und auf die sehr schnell zugegriffen werden kann, damit andere EUs in diesem CPU-Chip darauf zugreifen können. Es erfordert auch Threads im Betriebssystem, um zu entscheiden, zu welchem ​​Thread gewechselt werden soll. Schließlich, wenn eine EU Themen wechselt, es verliert die Leistungsgewinne des Pipelining, das die meisten Prozessorarchitekturen verwenden; Es muss die Pipeline leeren, bevor Threads gewechselt werden. Aber da all dies im Durchschnitt immer noch viel weniger Zeit in Anspruch nimmt als nur darauf zu warten, dass die Festplatte oder sogar der Arbeitsspeicher mit Informationen versorgt wird, sind die Kosten wert.

Wenn Sie jedoch die doppelte Anzahl "aktiver" Threads als EUs überschreiten, verwendet das Betriebssystem im Allgemeinen mehr Zeitplanungs-Threads der EUs, und die EUs verbringen mehr Zeit mit dem Umschalten zwischen diesen Threads als mit dem Ausführen aktiver Threads von Programmen. Dies ist der Punkt von Größenunterschieden; Es dauert länger, bis ein Multithread-Algorithmus ausgeführt wird, wenn Sie an dieser Stelle einen zusätzlichen Thread hinzufügen.

Insgesamt möchten Sie also mindestens so viele Threads in Ihrem Programm beibehalten, wie EUs auf dem Computer vorhanden sind, aber Sie möchten vermeiden, dass mehr als die doppelte Anzahl an Threads vorhanden ist, die nicht warten oder schlafen.

KeithS
quelle
Wenn N die Anzahl der Threads und U die Anzahl der Einheiten ist, stellte das OP die Regel "N = U" in Frage. Du entspannst es zu einer "U <= N <= 2 U" -Regel. Ich würde ein bisschen weiter gehen und sagen, dass "N <= c U" für eine "einigermaßen kleine" Konstante (dem Programmierer bekannt) akzeptabel ist (wenn die Benchmarks eine angemessene Leistung zeigen). Ich wäre sehr besorgt, wenn die Anzahl der Threads auf eine möglicherweise unbegrenzte Anzahl anwachsen könnte.
5gon12eder
1

Sie sollten einen Thread für Folgendes verwenden:

Jeder Prozessor, den Sie beschäftigen müssen.

Jedes E / A, das Sie sinnvollerweise gleichzeitig anhalten können, kann nicht blockierungsfrei ausgeführt werden. (Liest beispielsweise von einer lokalen Festplatte.)

Jede Aufgabe, die einen dedizierten Thread erfordert, z. B. das Aufrufen einer Bibliothek ohne nicht blockierende Schnittstelle oder wenn nicht blockierende Schnittstellen nicht geeignet sind. Dies umfasst Aufgaben wie das Überwachen der Systemuhr, das Auslösen von Timern usw.

Ein paar zusätzliche Maßnahmen zum Schutz vor unerwarteten Blockierungen wie Seitenfehlern.

Ein paar zusätzliche Maßnahmen zum Schutz vor erwarteten Blockierungen, die es nicht wert sind, optimiert zu werden, z. B. in unkritischem Code. (Wenn Sie beispielsweise sehr selten eine DNS-Anforderung ausführen müssen, lohnt es sich wahrscheinlich nicht, DNS-Anforderungen asynchron auszuführen. Erstellen Sie einfach ein paar zusätzliche Threads, und vereinfachen Sie Ihr Leben.)

Wenn Sie die Regel "Ein Thread pro Prozessor" befolgen, ist der gesamte Code leistungskritisch. Jeder Code, der aus irgendeinem Grund blockiert, bedeutet, dass Ihr Prozess diesen Prozessor nicht verwenden kann. Das macht das Programmieren ohne guten Grund sehr viel schwieriger.

David Schwartz
quelle
0

Sie können entweder Prozesse und Threads erzeugen, um die Verwendung eines Multicore \ Multiprozessorsystems für ein einzelnes Programm zu ermöglichen. In diesem Fall profitieren Sie (zumindest für das einzelne Programm) nicht davon, mehr Threads \ Prozesse als Kerne zu haben.

Oder Sie können Routinen haben, die nach einem Ereignis suchen, das normalerweise die weitere Ausführung blockiert. Statt die CPU mit einem Polling zu verbinden, können Sie stattdessen einen Thread erstellen, der sich im Leerlauf befindet, bis das entsprechende Ereignis ihn aufweckt. Diese Methode wird sehr häufig in Webservern und GUI-Ereigniswarteschlangen verwendet. Die meisten Programme möchten eine Art zentralen Datenspeicher haben (auch wenn der Programmausführungscode), auf den alle Threads zugreifen können. Ich denke, deshalb verwenden sie Threading über Prozesse.

Peter Smith
quelle
0

Die Anwendungen , die Sie erwähnen , sind selten laufen alle diese zehn Threads gleichzeitig. Die meisten von ihnen sitzen einfach da, weil sie in einem Thread-Pool sind . Die App sendet verschiedene Aufgaben an eine Warteschlange, die von Threads im Thread-Pool gelöscht wird.

Warum ist der Pool dann so groß? Da Threads häufig auf andere Ressourcen wie Festplatte, Netzwerk, Benutzer, einen anderen Thread usw. warten müssen. Während ein Thread wartet, sollten andere Threads ausgeführt werden, um den Prozessor voll auszunutzen. Die richtige Dimensionierung des Pools ist jedoch schwierig. Bei zu wenigen Threads geht die Leistung verloren, da der Prozessor beim Warten auf etwas nicht voll ausgelastet ist. Zu viele Threads, und Sie verlieren die Leistung, weil Sie zwischen ihnen wechseln.

Joonas Pulakka
quelle