Threads vs Prozesse unter Linux


253

Ich habe kürzlich einige Leute sagen hören, dass es unter Linux fast immer besser ist, Prozesse anstelle von Threads zu verwenden, da Linux Prozesse sehr effizient handhabt und mit Threads so viele Probleme (wie das Sperren) verbunden sind. Ich bin jedoch misstrauisch, da Threads in einigen Situationen einen ziemlich großen Leistungsgewinn bringen könnten.

Meine Frage ist also, ob ich in einer Situation, in der Threads und Prozesse ziemlich gut umgehen können, Prozesse oder Threads verwenden sollte. Wenn ich beispielsweise einen Webserver schreibe, sollte ich Prozesse oder Threads (oder eine Kombination) verwenden?


Gibt es einen Unterschied zu Linux 2.4?
Mouviciel

3
Der Unterschied zwischen Prozessen und Threads unter Linux 2.4 besteht darin, dass Threads mehr Teile ihres Status (Adressraum, Dateihandles usw.) gemeinsam nutzen als Prozesse, die dies normalerweise nicht tun. Die NPTL unter Linux 2.6 macht dies etwas klarer, indem sie ihnen "Thread-Gruppen" geben, die ein bisschen wie "Prozesse" in Win32 und Solaris sind.
MarkR

6
Die gleichzeitige Programmierung ist schwierig. Wenn Sie keine sehr hohe Leistung benötigen , ist der wichtigste Aspekt Ihres Kompromisses häufig die Schwierigkeit des Debuggens . Prozesse erleichtern diesbezüglich die Lösung erheblich, da die gesamte Kommunikation explizit erfolgt (einfach zu überprüfen, zu protokollieren usw.). Im Gegensatz dazu erzeugt der gemeinsame Speicher von Threads Unmengen von Stellen, an denen ein Thread fälschlicherweise einen anderen beeinflussen kann.
Lutz Prechelt

1
@LutzPrechelt - Die gleichzeitige Programmierung kann sowohl mit mehreren Threads als auch mit mehreren Prozessen erfolgen. Ich verstehe nicht, warum Sie davon ausgehen, dass die gleichzeitige Programmierung nur Multithreading ist. Es kann an bestimmten Sprachbeschränkungen liegen, aber im Allgemeinen kann es beides sein.
iankit

2
Ich verlinke Lutz erklärte lediglich, dass die gleichzeitige Programmierung schwierig ist, je nachdem, welcher Prozess gewählt wird - Prozess oder Threads -, aber dass die gleichzeitige Programmierung unter Verwendung von Prozessen in vielen Fällen das Debuggen erleichtert.
user2692263

Antworten:


322

Linux verwendet ein 1-1-Threading-Modell, bei dem (für den Kernel) nicht zwischen Prozessen und Threads unterschieden wird - alles ist einfach eine ausführbare Aufgabe. * *

Unter Linux klont der Systemaufruf cloneeine Aufgabe mit einer konfigurierbaren Freigabeebene, darunter:

  • CLONE_FILES: Verwenden Sie dieselbe Dateideskriptortabelle (anstatt eine Kopie zu erstellen).
  • CLONE_PARENT: Richten Sie keine Eltern-Kind-Beziehung zwischen der neuen und der alten Aufgabe ein (andernfalls Kind getppid()= Eltern getpid())
  • CLONE_VM: denselben Speicherplatz gemeinsam nutzen (anstatt eine COW- Kopie zu erstellen )

fork()ruft am clone(wenigsten teilen )und pthread_create()ruft clone(am meisten teilen auf ). ** **.

forkDas pthread_createKopieren von Tabellen und das Erstellen von COW-Zuordnungen für den Speicher kostet ein kleines bisschen mehr als das Kopieren von Tabellen, aber die Linux-Kernel-Entwickler haben versucht (und es geschafft), diese Kosten zu minimieren.

Das Wechseln zwischen Aufgaben, wenn sie denselben Speicherplatz und verschiedene Tabellen gemeinsam nutzen, ist ein kleines bisschen billiger als wenn sie nicht gemeinsam genutzt werden, da die Daten möglicherweise bereits in den Cache geladen sind. Das Wechseln von Aufgaben ist jedoch immer noch sehr schnell, auch wenn nichts gemeinsam genutzt wird. Dies ist etwas anderes, das Linux-Kernel-Entwickler sicherstellen möchten (und das sie erfolgreich sicherstellen können).

In der Tat, wenn Sie auf einem Multi-Prozessor - System sind, nicht kann Sharing Leistung tatsächlich von Vorteil: wenn jede Aufgabe auf einem anderen Prozessor ausgeführt wird , gemeinsam genutzten Speicher teuer ist synchronisiert.


* Vereinfacht. CLONE_THREADbewirkt CLONE_SIGHAND, dass die Signalübertragung gemeinsam genutzt wird (was benötigt wird , wodurch die Signalhandlertabelle gemeinsam genutzt wird).

** Vereinfacht. Es gibt sowohl SYS_forkals auch SYS_clonesyscalls, aber im Kernel sind die sys_forkund sys_clonebeide sehr dünne Wrapper um dieselbe do_forkFunktion, die selbst ein dünner Wrapper ist copy_process. Ja, die Begriffe process, threadund tasksind eher austauschbar in dem Linux - Kernel verwendet ...


6
Ich denke, wir vermissen 1 Punkt. Wenn Sie mehrere Prozesse für Ihren Webserver ausführen, müssen Sie einen anderen Prozess schreiben, um den Socket zu öffnen und "Arbeit" an verschiedene Threads zu übergeben. Das Einfädeln bietet einen einzigen Prozess, mehrere Gewinde, sauberes Design. In vielen Situationen ist Thread nur natürlich und in anderen Situationen ist ein neuer Prozess nur natürlich. Wenn das Problem in eine Grauzone fällt, werden die anderen durch Ephemient erklärten Kompromisse wichtig.
Saurabh

26
@ Saurabh Nicht wirklich. Sie können ganz einfach socket, bind, listen, fork, und haben dann mehrere Prozesse acceptVerbindungen auf demselben Socket. Ein Prozess kann die Annahme beenden, wenn er ausgelastet ist, und der Kernel leitet eingehende Verbindungen an einen anderen Prozess weiter (wenn niemand zuhört, wird der Kernel je nach listenRückstand in die Warteschlange gestellt oder gelöscht). Sie haben nicht viel mehr Kontrolle über die Arbeitsverteilung als das, aber normalerweise ist das gut genug!
Ephemient

2
@Bloodcount Alle Prozesse / Threads unter Linux werden nach demselben Mechanismus erstellt, der einen vorhandenen Prozess / Thread klont. Flags übergeben, clone()um zu bestimmen, welche Ressourcen gemeinsam genutzt werden. Eine Aufgabe kann auch unshare()zu einem späteren Zeitpunkt Ressourcen bereitstellen.
Ephemient

4
@KarthikBalaguru Innerhalb des Kernels selbst gibt es task_structfür jede Aufgabe eine. Dies wird im gesamten Kernel-Code häufig als "Prozess" bezeichnet, entspricht jedoch jedem ausführbaren Thread. Es gibt keine process_struct; Wenn eine Reihe von task_structs durch ihre thread_groupListe miteinander verbunden sind , sind sie der gleiche "Prozess" für den Benutzerbereich. Es gibt ein wenig spezielle Behandlung von "Threads", z. B. werden alle Geschwister-Threads auf Fork und Exec gestoppt und nur der "Haupt" -Thread wird in angezeigt ls /proc. Auf jeden Thread kann jedoch zugegriffen werden /proc/pid, unabhängig davon, ob er in aufgeführt ist /procoder nicht.
Ephemient

5
@KarthikBalaguru Der Kernel unterstützt ein Kontinuum des Verhaltens zwischen Threads und Prozessen. Zum Beispiel clone(CLONE_THREAD | CLONE_VM | CLONE_SIGHAND))würden Sie einen neuen "Thread" erhalten, der kein Arbeitsverzeichnis, keine Dateien oder Sperren gemeinsam nutzt, während clone(CLONE_FILES | CLONE_FS | CLONE_IO)Sie einen "Prozess" erhalten, der dies tut. Das zugrunde liegende System erstellt Aufgaben durch Klonen. fork()und pthread_create()sind nur Bibliotheksfunktionen, die clone()anders aufrufen (wie ich in dieser Antwort geschrieben habe).
Ephemient

60

Linux (und in der Tat Unix) bietet Ihnen eine dritte Option.

Option 1 - Prozesse

Erstellen Sie eine eigenständige ausführbare Datei, die einen Teil (oder alle Teile) Ihrer Anwendung verarbeitet, und rufen Sie sie für jeden Prozess separat auf, z. B. führt das Programm Kopien von sich selbst aus, an die Aufgaben delegiert werden sollen.

Option 2 - Threads

Erstellen Sie eine eigenständige ausführbare Datei, die mit einem einzelnen Thread startet, und erstellen Sie zusätzliche Threads, um einige Aufgaben auszuführen

Option 3 - Gabel

Nur unter Linux / Unix verfügbar, das ist etwas anders. Ein gegabelter Prozess ist wirklich ein eigener Prozess mit einem eigenen Adressraum - es gibt nichts, was das Kind (normalerweise) tun kann, um den Adressraum seiner Eltern oder Geschwister zu beeinflussen (im Gegensatz zu einem Thread) -, sodass Sie zusätzliche Robustheit erhalten.

Die Speicherseiten werden jedoch nicht kopiert, sondern beim Schreiben kopiert, sodass normalerweise weniger Speicher verwendet wird, als Sie sich vorstellen können.

Stellen Sie sich ein Webserverprogramm vor, das aus zwei Schritten besteht:

  1. Konfigurations- und Laufzeitdaten lesen
  2. Seitenanfragen bedienen

Wenn Sie Threads verwendet haben, wird Schritt 1 einmal und Schritt 2 in mehreren Threads ausgeführt. Wenn Sie "traditionelle" Prozesse verwenden, müssen die Schritte 1 und 2 für jeden Prozess wiederholt und der Speicher zum Speichern der Konfigurations- und Laufzeitdaten dupliziert werden. Wenn Sie fork () verwendet haben, können Sie Schritt 1 einmal und dann fork () ausführen, wobei die Laufzeitdaten und die Konfiguration im Speicher unberührt bleiben und nicht kopiert werden.

Es gibt also wirklich drei Möglichkeiten.


7
@Qwertie Forking ist nicht so cool, es bricht viele Bibliotheken auf subtile Weise (wenn Sie sie im übergeordneten Prozess verwenden). Es entsteht unerwartetes Verhalten, das selbst erfahrene Programmierer verwirrt.
MarkR

2
@MarkR Könnten Sie einige Beispiele oder einen Link geben, wie das Gabeln die Bibliothek unterbricht und unerwartetes Verhalten erzeugt?
Ehtesh Choudhury

18
Wenn sich ein Prozess mit einer offenen MySQL-Verbindung teilt, passieren schlimme Dinge, da der Socket von zwei Prozessen gemeinsam genutzt wird. Selbst wenn nur ein Prozess die Verbindung verwendet, verhindert der andere, dass sie geschlossen wird.
MarkR

1
Der Systemaufruf fork () wird von POSIX angegeben (was bedeutet, dass er auf allen Unix-Systemen verfügbar ist). Wenn Sie die zugrunde liegende Linux-API verwendet haben, bei der es sich um den Systemaufruf clone () handelt, haben Sie unter Linux sogar noch mehr Auswahlmöglichkeiten als nur die drei .
Lie Ryan

2
@MarkR Die gemeinsame Nutzung des Sockets ist beabsichtigt. Außerdem kann jeder der Prozesse den Socket mit linux.die.net/man/2/shutdown schließen, bevor close () für den Socket aufgerufen wird.
Lelanthran

53

Das hängt von vielen Faktoren ab. Prozesse sind schwerer als Threads und verursachen höhere Kosten für das Starten und Herunterfahren. Interprozesskommunikation (IPC) ist auch schwieriger und langsamer als Interthread-Kommunikation.

Umgekehrt sind Prozesse sicherer und sicherer als Threads, da jeder Prozess in einem eigenen virtuellen Adressraum ausgeführt wird. Wenn ein Prozess abstürzt oder ein Pufferüberlauf auftritt, wirkt sich dies überhaupt nicht auf einen anderen Prozess aus. Wenn ein Thread abstürzt, werden alle anderen Threads im Prozess heruntergefahren, und wenn ein Thread einen Pufferüberlauf aufweist, wird er geöffnet eine Sicherheitslücke in allen Fäden.

Wenn die Module Ihrer Anwendung größtenteils unabhängig und mit wenig Kommunikation ausgeführt werden können, sollten Sie wahrscheinlich Prozesse verwenden, wenn Sie sich die Kosten für das Starten und Herunterfahren leisten können. Der Leistungseinbruch von IPC ist minimal und Sie sind etwas sicherer gegen Fehler und Sicherheitslücken. Wenn Sie jede Leistung benötigen, die Sie erhalten können, oder viele gemeinsam genutzte Daten (z. B. komplexe Datenstrukturen) haben, verwenden Sie Threads.


9
Adams Antwort würde gut als Briefing für Führungskräfte dienen. Für weitere Einzelheiten bieten MarkR und Ephemient gute Erklärungen. Eine sehr ausführliche Erklärung mit Beispielen finden Sie unter cs.cf.ac.uk/Dave/C/node29.html , scheint jedoch teilweise etwas veraltet zu sein.
CyberFonic

2
CyberFonic gilt für Windows. Wie Ephemient unter Linux sagt, sind Prozesse nicht schwerer. Unter Linux stehen alle für die Kommunikation zwischen Threads verfügbaren Mechanismen (Futex, Shared Memory, Pipes, IPC) auch für Prozesse zur Verfügung und werden mit derselben Geschwindigkeit ausgeführt.
Russell Stuart

IPC ist schwieriger zu verwenden, aber was ist, wenn jemand "Shared Memory" verwendet?
Abhiarora

11

Andere haben die Überlegungen diskutiert.

Möglicherweise besteht der wichtige Unterschied darin, dass in Windows-Prozessen im Vergleich zu Threads schwere und teure Prozesse ausgeführt werden und in Linux der Unterschied viel geringer ist, sodass die Gleichung an einem anderen Punkt ausgeglichen wird.


9

Es war einmal Unix und in diesem guten alten Unix gab es viel Aufwand für Prozesse. Einige clevere Leute haben also Threads erstellt, die denselben Adressraum mit dem übergeordneten Prozess teilen und nur einen reduzierten Kontext benötigen switch, wodurch der Kontextwechsel effizienter wird.

In einem modernen Linux (2.6.x) gibt es keinen großen Leistungsunterschied zwischen einem Kontextwechsel eines Prozesses und einem Thread (nur das MMU-Material ist für den Thread zusätzlich). Es gibt ein Problem mit dem gemeinsam genutzten Adressraum. Dies bedeutet, dass ein fehlerhafter Zeiger in einem Thread den Speicher des übergeordneten Prozesses oder eines anderen Threads im selben Adressraum beschädigen kann.

Ein Prozess ist durch die MMU geschützt, sodass ein fehlerhafter Zeiger nur ein Signal 11 und keine Beschädigung verursacht.

Ich würde im Allgemeinen Prozesse verwenden (nicht viel Overhead für Kontextwechsel unter Linux, aber Speicherschutz aufgrund von MMU), aber pthreads, wenn ich eine Echtzeit-Scheduler-Klasse benötigen würde, die insgesamt eine andere Tasse Tee ist.

Warum haben Threads unter Linux Ihrer Meinung nach einen so großen Leistungsgewinn? Haben Sie Daten dafür oder ist es nur ein Mythos?


1
Ja, ich habe einige Daten. Ich habe einen Test ausgeführt, der 100.000 Prozesse erstellt, und einen Test, der 100.000 Threads erstellt. Die Thread-Version lief ungefähr 9x schneller (17,38 Sekunden für Prozesse, 1,93 Sekunden für Threads). Dies testet nur die Erstellungszeit, aber für kurzlebige Aufgaben kann die Erstellungszeit der Schlüssel sein.
user17918

4
@ user17918 - Ist es möglich, dass Sie den Code teilen, den Sie zur Berechnung der oben genannten Zeiten verwenden.
Codingfreak

Ein großer Unterschied: Bei Prozessen erstellt der Kernel für jeden Prozess eine Seitentabelle, und die Köpfe verwenden nur eine Seitentabelle. Ich denke, es ist normal, dass die Threads schneller sind als die Prozesse
c4f4t0r

Eine andere einfache Sichtweise ist, dass TCB ziemlich kleiner als PCB ist. Daher ist es offensichtlich, dass ein Prozesskontextwechsel, bei dem PCB beteiligt ist, etwas mehr Zeit in Anspruch nimmt als das Wechseln von Threads.
Karthik Balaguru

5

Wie eng sind Ihre Aufgaben gekoppelt?

Wenn sie unabhängig voneinander leben können, verwenden Sie Prozesse. Wenn sie sich aufeinander verlassen, verwenden Sie Threads. Auf diese Weise können Sie einen fehlerhaften Prozess beenden und neu starten, ohne den Betrieb der anderen Aufgaben zu beeinträchtigen.


4

Um die Sache noch weiter zu verkomplizieren, gibt es einen threadlokalen Speicher und einen gemeinsam genutzten Unix-Speicher.

Durch den threadlokalen Speicher kann jeder Thread eine separate Instanz globaler Objekte haben. Das einzige Mal, dass ich es verwendet habe, war beim Erstellen einer Emulationsumgebung unter Linux / Windows für Anwendungscode, der in einem RTOS ausgeführt wurde. Im RTOS war jede Aufgabe ein Prozess mit einem eigenen Adressraum, in der Emulationsumgebung war jede Aufgabe ein Thread (mit einem gemeinsam genutzten Adressraum). Durch die Verwendung von TLS für Dinge wie Singletons konnten wir für jeden Thread eine eigene Instanz haben, genau wie in der "echten" RTOS-Umgebung.

Shared Memory kann Ihnen (offensichtlich) die Leistungsvorteile bieten, wenn mehrere Prozesse auf denselben Speicher zugreifen, jedoch auf Kosten / Risiko einer ordnungsgemäßen Synchronisierung der Prozesse. Eine Möglichkeit, dies zu tun, besteht darin, dass ein Prozess eine Datenstruktur im gemeinsam genutzten Speicher erstellt und dann über die herkömmliche Kommunikation zwischen Prozessen (wie eine Named Pipe) ein Handle an diese Struktur sendet.


1
Ich habe den Thread-lokalen Speicher für eine Statistiksammlung verwendet, als ich das letzte Mal ein Thread-Netzwerkprogramm geschrieben habe: Jeder Thread hat an seine eigenen Zähler geschrieben, es sind keine Sperren erforderlich, und nur wenn Nachrichten gesendet werden, würde jeder Thread seine Statistiken zu den globalen Summen kombinieren. Aber ja, TLS wird nicht sehr häufig verwendet oder ist notwendig. Shared Memory andererseits ... Sie können nicht nur Daten effizient senden, sondern auch POSIX-Semaphore zwischen Prozessen gemeinsam nutzen, indem Sie sie im Shared Memory ablegen. Es ist ziemlich erstaunlich.
Ephemient

4

In meiner jüngsten Arbeit mit LINUX ist eine Sache zu beachten, sind Bibliotheken. Wenn Sie Threads verwenden, stellen Sie sicher, dass alle Bibliotheken, die Sie über Threads hinweg verwenden können, threadsicher sind. Das hat mich ein paar Mal verbrannt. Insbesondere ist libxml2 nicht sofort threadsicher. Es kann mit thread safe kompiliert werden, aber das ist nicht das, was Sie mit aptitude install erhalten.


3

Ich muss dem zustimmen, was Sie gehört haben. Wenn wir unseren Cluster ( xhplund dergleichen) vergleichen, erzielen wir mit Prozessen über Threads immer eine deutlich bessere Leistung.</anecdote>


3

Die Entscheidung zwischen Thread / Prozess hängt ein wenig davon ab, wofür Sie ihn verwenden werden. Einer der Vorteile eines Prozesses besteht darin, dass er eine PID hat und getötet werden kann, ohne dass auch der Elternteil beendet wird.

Für ein reales Beispiel eines Webservers unterstützte Apache 1.3 nur mehrere Prozesse. In 2.0 wurde jedoch eine Abstraktion hinzugefügt, sodass Sie zwischen beiden wechseln können. Kommentare scheint zu bestätigen , dass Prozesse robuster sind aber Threads kann ein wenig bessere Leistung ( mit Ausnahme von Fenstern , in denen die Leistung für Prozesse saugt und Sie wollen nur Threads verwenden).


2

In den meisten Fällen würde ich Prozesse Threads vorziehen. Threads können nützlich sein, wenn Sie eine relativ kleinere Aufgabe haben (Prozessaufwand >> Zeit, die von jeder geteilten Aufgabeneinheit benötigt wird) und eine gemeinsame Nutzung des Speichers zwischen ihnen erforderlich ist. Denken Sie an eine große Auswahl. Beachten Sie auch (offtopic), dass Multithreading oder -verarbeitung keinen Nutzen bringen, wenn Ihre CPU-Auslastung 100 Prozent oder nahe daran liegt. (in der Tat wird es sich verschlechtern)


Was meinst du ohne Nutzen? Wie wäre es mit umfangreichen Berechnungen im GUI-Thread? Das Verschieben in einen parallelen Thread ist aus Sicht der Benutzererfahrung viel besser, unabhängig davon, wie die CPU geladen ist.
Olegst

2

Threads -> Threads teilen sich einen Speicherplatz, es ist eine Abstraktion der CPU, es ist leichtgewichtig. Prozesse -> Prozesse haben ihren eigenen Speicherplatz, es ist eine Abstraktion eines Computers. Um die Aufgabe zu parallelisieren, müssen Sie eine CPU abstrahieren. Die Vorteile der Verwendung eines Prozesses gegenüber einem Thread sind jedoch Sicherheit und Stabilität, während ein Thread weniger Speicher als der Prozess benötigt und eine geringere Latenz bietet. Ein Beispiel in Bezug auf Web wäre Chrom und Firefox. Im Falle von Chrome ist jede Registerkarte ein neuer Prozess, daher ist die Speichernutzung von Chrome höher als bei Firefox, während die bereitgestellte Sicherheit und Stabilität besser ist als bei Firefox. Die hier von Chrome bereitgestellte Sicherheit ist besser, da jede Registerkarte ein neuer Prozess ist. Eine andere Registerkarte kann nicht in den Speicherbereich eines bestimmten Prozesses eindringen.


2

Ich denke, jeder hat großartige Arbeit geleistet, um auf Ihre Frage zu antworten. Ich füge nur weitere Informationen zu Thread versus Prozess unter Linux hinzu, um einige der vorherigen Antworten im Zusammenhang mit dem Kernel zu klären und zusammenzufassen. Meine Antwort bezieht sich also auf kernelspezifischen Code unter Linux. Laut der Linux-Kernel-Dokumentation gibt es keine klare Unterscheidung zwischen Thread und Prozess, außer dass der Thread im Gegensatz zum Prozess einen gemeinsam genutzten virtuellen Adressraum verwendet. Beachten Sie auch, dass der Linux-Kernel den Begriff "Aufgabe" verwendet, um sich allgemein auf Prozess und Thread zu beziehen.

"Es gibt keine internen Strukturen, die Prozesse oder Threads implementieren. Stattdessen gibt es eine struct task_struct, die eine abstrakte Planungseinheit namens task beschreibt."

Laut Linus Torvalds sollten Sie auch NICHT an Prozess oder Thread denken, da dies zu einschränkend ist und der einzige Unterschied darin besteht, COE oder Ausführungskontext in Bezug auf "Trennen des Adressraums vom übergeordneten" oder gemeinsam genutzten Adressraum. In der Tat benutzt er ein Web - Server Beispiel zu seinem Punkt zu machen hier (die Lektüre sehr empfehlen).

Volle Gutschrift für die Linux-Kernel-Dokumentation


-3

Wenn Sie Ressourcen gemeinsam nutzen müssen, sollten Sie wirklich Threads verwenden.

Berücksichtigen Sie auch die Tatsache, dass Kontextwechsel zwischen Threads viel billiger sind als Kontextwechsel zwischen Prozessen.

Ich sehe keinen Grund, explizit getrennte Prozesse durchzuführen, es sei denn, Sie haben einen guten Grund dafür (Sicherheit, bewährte Leistungstests usw.)


3
Ich habe den Repräsentanten zum Bearbeiten, aber ich stimme nicht ganz zu. Kontextwechsel zwischen Prozessen unter Linux sind fast so billig wie Kontextwechsel zwischen Threads.
Ephemient
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.