Welche Ressourcen werden von Threads gemeinsam genutzt?

264

Kürzlich wurde mir in einem Interview eine Frage gestellt, was der Unterschied zwischen einem Prozess und einem Thread ist. Wirklich, ich wusste die Antwort nicht. Ich dachte eine Minute nach und gab eine sehr seltsame Antwort.

Threads teilen sich den gleichen Speicher, Prozesse nicht. Nachdem ich darauf geantwortet hatte, schenkte mir der Interviewer ein böses Lächeln und warf mir folgende Fragen zu:

F : Kennen Sie die Segmente , in denen ein Programm geteilt wird?

Meine Antwort: Ja (dachte, es war einfach) Stapel, Daten, Code, Haufen

F. Sagen Sie mir also: Welche Segmente teilen sich Threads?

Ich konnte das nicht beantworten und sagte schließlich alle.

Kann jemand die richtigen und eindrucksvollen Antworten auf den Unterschied zwischen einem Prozess und einem Thread geben?

Xinus
quelle
9
Threads haben denselben virtuellen Adressraum , der Prozess jedoch nicht.
Benoit

Antworten:

177

Sie sind ziemlich korrekt, aber Threads teilen sich alle Segmente außer dem Stapel. Threads haben unabhängige Aufrufstapel, jedoch ist der Speicher in anderen Threadstapeln weiterhin verfügbar, und theoretisch könnten Sie einen Zeiger auf den Speicher im lokalen Stapelrahmen eines anderen Threads halten (obwohl Sie wahrscheinlich einen besseren Platz finden sollten, um diesen Speicher zu platzieren!).

Greg Hewgill
quelle
27
Der interessante Teil ist, dass, obwohl Threads unabhängige Aufrufstapel haben, der Speicher in anderen Stapeln immer noch zugänglich ist.
Karthik Balaguru
1
Ja, ich frage mich, ob es akzeptabel ist, auf Speicher in anderen Stapeln zwischen Threads zuzugreifen. Solange Sie sicher sind, dass Sie nicht versuchen, auf einen freigegebenen Stapel zu verweisen, bin ich nicht sicher, ob ich ein Problem damit sehe?
bph
2
@bph: Es ist möglich , auf den Stapelspeicher eines anderen Threads zuzugreifen, aber im Interesse einer guten Softwareentwicklungspraxis würde ich nicht sagen, dass dies akzeptabel ist .
Greg Hewgill
1
Der Zugriff auf die Stapel anderer Threads, insbesondere das Schreiben auf diese, führt zu Problemen mit mehreren Garbage Collector-Implementierungen. Dies könnte jedoch als Fehler der GC-Implementierung gerechtfertigt sein.
Yyny
56

Aus Wikipedia (Ich denke, das wäre eine wirklich gute Antwort für den Interviewer: P)

Threads unterscheiden sich von herkömmlichen Multitasking-Betriebssystemprozessen darin, dass:

  • Prozesse sind normalerweise unabhängig, während Threads als Teilmengen eines Prozesses existieren
  • Prozesse enthalten beträchtliche Statusinformationen, während mehrere Threads innerhalb eines Prozesses den Status sowie Speicher und andere Ressourcen gemeinsam nutzen
  • Prozesse haben separate Adressräume, während Threads ihren Adressraum gemeinsam nutzen
  • Prozesse interagieren nur über vom System bereitgestellte prozessübergreifende Kommunikationsmechanismen.
  • Die Kontextumschaltung zwischen Threads im selben Prozess ist normalerweise schneller als die Kontextumschaltung zwischen Prozessen.
Jorge Córdoba
quelle
2
zu Punkt 2 oben: Für Threads unterhält auch die CPU einen Kontext.
Jack
49

Es muss wirklich darauf hingewiesen werden, dass diese Frage zwei Aspekte hat - den theoretischen Aspekt und den Implementierungsaspekt.

Betrachten wir zunächst den theoretischen Aspekt. Sie müssen verstehen, was ein Prozess konzeptionell ist, um den Unterschied zwischen einem Prozess und einem Thread zu verstehen und was zwischen ihnen geteilt wird.

Wir haben Folgendes aus Abschnitt 2.2.2 Das klassische Gewindemodell in modernen Betriebssystemen 3e von Tanenbaum:

Das Prozessmodell basiert auf zwei unabhängigen Konzepten: Ressourcengruppierung und -ausführung. Manchmal ist es nützlich, sie zu trennen; Hier kommen Threads ins Spiel ....

Er fährt fort:

Eine Möglichkeit, einen Prozess zu betrachten, besteht darin, verwandte Ressourcen zu gruppieren. Ein Prozess verfügt über einen Adressraum, der Programmtext und -daten sowie andere Ressourcen enthält. Diese Ressourcen können geöffnete Dateien, untergeordnete Prozesse, ausstehende Alarme, Signalhandler, Buchhaltungsinformationen und mehr enthalten. Durch die Zusammenstellung in Form eines Prozesses können sie einfacher verwaltet werden. Das andere Konzept eines Prozesses ist ein Ausführungsthread, der normalerweise auf nur einen Thread verkürzt wird. Der Thread verfügt über einen Programmzähler, der verfolgt, welche Anweisung als nächstes ausgeführt werden soll. Es hat Register, die seine aktuellen Arbeitsvariablen enthalten. Es verfügt über einen Stapel, der den Ausführungsverlauf enthält, mit einem Frame für jede aufgerufene, aber noch nicht zurückgegebene Prozedur. Obwohl ein Thread in einem bestimmten Prozess ausgeführt werden muss, Der Thread und sein Prozess sind unterschiedliche Konzepte und können separat behandelt werden. Prozesse werden verwendet, um Ressourcen zu gruppieren. Threads sind die Entitäten, deren Ausführung auf der CPU geplant ist.

Weiter unten liefert er die folgende Tabelle:

Per process items             | Per thread items
------------------------------|-----------------
Address space                 | Program counter
Global variables              | Registers
Open files                    | Stack
Child processes               | State
Pending alarms                |
Signals and signal handlers   |
Accounting information        |

Das Obige ist das, was Sie brauchen, damit Threads funktionieren. Wie andere bereits betont haben, handelt es sich bei Segmenten beispielsweise um betriebssystemabhängige Implementierungsdetails.

Robert S. Barnes
quelle
2
Dies ist eine großartige Erklärung. Aber es sollte wahrscheinlich irgendwie mit der Frage verbunden sein, als "Antwort" betrachtet zu werden
Katalysator294
Ist der Programmzähler in Bezug auf die Tabelle kein Register? und der "Zustand" eines Threads, erfasst im Wert der Register? Mir fehlt auch der Zeiger auf den Code, den sie ausführen (Zeiger auf den Prozesstext)
nur
29

Sagen Sie dem Interviewer, dass dies ausschließlich von der Implementierung des Betriebssystems abhängt.

Nehmen Sie zum Beispiel Windows x86. Es gibt nur 2 Segmente [1], Code und Daten. Und beide sind dem gesamten Adressraum von 2 GB (linear, Benutzer) zugeordnet. Basis = 0, Limit = 2 GB. Sie hätten einen erstellt, aber x86 erlaubt nicht, dass ein Segment sowohl Lesen / Schreiben als auch Ausführen ist. Also machten sie zwei und setzten CS so, dass es auf den Code-Deskriptor zeigt, und den Rest (DS, ES, SS usw.), um auf den anderen zu zeigen [2]. Aber beide zeigen auf das gleiche Zeug!

Die Person, die Sie interviewt hat, hat eine versteckte Annahme gemacht, dass sie nicht angegeben hat, und das ist ein dummer Trick.

Also bezüglich

Frage: Sagen Sie mir also, welche Segment-Thread-Freigabe?

Die Segmente sind für die Frage zumindest unter Windows irrelevant. Threads teilen sich den gesamten Adressraum. Es gibt nur 1 Stapelsegment, SS, und es zeigt genau dasselbe, was DS, ES und CS tun [2]. Dh der ganze blutige User Space . 0-2 GB. Das bedeutet natürlich nicht, dass Threads nur einen Stapel haben. Natürlich hat jedes seinen eigenen Stapel, aber x86-Segmente werden für diesen Zweck nicht verwendet.

Vielleicht macht * nix etwas anderes. Wer weiß. Die Prämisse, auf der die Frage beruhte, war gebrochen.


  1. Zumindest für den Benutzerraum.
  2. Von ntsd notepad:cs=001b ss=0023 ds=0023 es=0023
Alex Budovski
quelle
1
Ja ... Segmente hängen vom Betriebssystem und dem Compiler / Linker ab. Manchmal gibt es ein vom DATA-Segment getrenntes BSS-Segment. Manchmal gibt es RODATA (Daten wie konstante Zeichenfolgen, die sich auf schreibgeschützten Seiten befinden können). Einige Systeme unterteilen DATA sogar in SMALL DATA (Zugriff über eine Basis + 16-Bit-Offset) und (FAR) DATA (32-Bit-Offset für den Zugriff erforderlich). Es ist auch möglich, dass es ein zusätzliches TLS-DATENSegment (Thread Local Store) gibt, das pro Thread generiert wird
Adisak
5
Ah nein! Sie verwechseln Segmente mit Abschnitten! In Abschnitten beschreibt der Linker das Modul wie beschrieben in Teile (Daten, Daten, Text, BSS usw.). Aber ich spreche von Segmenten, wie in Intel / AMD x86-Hardware angegeben. Überhaupt nicht mit Compilern / Linkern verwandt. Hoffe das macht Sinn.
Alex Budovski
Adisak hat jedoch Recht mit dem Thread Local Store. Es ist für den Thread privat und wird nicht freigegeben. Ich kenne das Windows-Betriebssystem und bin mir anderer Betriebssysteme nicht sicher.
Jack
20

Im Allgemeinen werden Fäden als leichter Prozess bezeichnet. Wenn wir den Speicher in drei Abschnitte unterteilen, ist dies: Code, Daten und Stapel. Jeder Prozess hat seine eigenen Code-, Daten- und Stapelabschnitte und aufgrund dieses Kontexts ist die Umschaltzeit etwas hoch. Um die Kontextwechselzeit zu verkürzen, haben die Benutzer ein Thread-Konzept entwickelt, das Daten- und Codesegmente mit anderen Threads / Prozessen teilt und über ein eigenes STACK-Segment verfügt.

Nimish Thakkar
quelle
Du hast Haufen vergessen. Heap, wenn ich mich nicht irre, sollte zwischen Threads geteilt werden
Phate
20

Ein Prozess hat Code-, Daten-, Heap- und Stapelsegmente. Der Instruction Pointer (IP) eines Threads ODER eines Threads zeigt nun auf das Codesegment des Prozesses. Die Daten- und Heap-Segmente werden von allen Threads gemeinsam genutzt. Was ist nun mit dem Stapelbereich? Was ist eigentlich die Stapelfläche? Es ist ein Bereich, der vom Prozess nur für die Verwendung durch seinen Thread erstellt wurde ... da Stapel viel schneller als Heaps usw. verwendet werden können. Der Stapelbereich des Prozesses wird auf Threads aufgeteilt, dh wenn 3 Threads vorhanden sind, dann die Der Stapelbereich des Prozesses ist in 3 Teile unterteilt und jeweils den 3 Fäden zugeordnet. Mit anderen Worten, wenn wir sagen, dass jeder Thread seinen eigenen Stapel hat, ist dieser Stapel tatsächlich ein Teil des Prozessstapelbereichs, der jedem Thread zugewiesen ist. Wenn ein Thread seine Ausführung beendet hat, wird der Stapel des Threads vom Prozess zurückgefordert. Eigentlich, Nicht nur der Stapel eines Prozesses wird auf Threads aufgeteilt, sondern alle Register, die ein Thread wie SP-, PC- und Statusregister verwendet, sind die Register des Prozesses. Wenn es um die Freigabe geht, werden Code-, Daten- und Heap-Bereiche gemeinsam genutzt, während der Stapelbereich nur auf Threads aufgeteilt wird.

Dhirendra Vikash Sharma
quelle
13

Threads teilen sich die Code- und Datensegmente und den Heap, aber nicht den Stapel.

Kevin Peterson
quelle
11
Es gibt einen Unterschied zwischen "Zugriff auf Daten im Stapel" und "Freigabe des Stapels". Diese Threads haben ihre eigenen Stapel, die beim Aufrufen von Methoden verschoben und gelöscht werden.
Kevin Peterson
2
Sie sind beide gleichermaßen gültige Ansichten. Ja, jeder Thread hat seinen eigenen Stapel in dem Sinne, dass es eine Eins-zu-Eins-Entsprechung zwischen Threads und Stapeln gibt und jeder Thread einen Platz hat, den er für seine eigene normale Stapelverwendung verwendet. Sie sind aber auch vollständig gemeinsam genutzte Prozessressourcen, und auf Wunsch kann jeder Thread genauso einfach auf den Stapel eines anderen Threads zugreifen wie auf seinen eigenen.
David Schwartz
@DavidSchwartz, kann ich Ihren Punkt wie folgt zusammenfassen: Jeder Thread hat seinen eigenen Stapel, und der Stapel besteht aus 2 Teilen - dem ersten Teil, der von Threads gemeinsam genutzt wird, bevor der Prozess Multithreading ausführt, und dem zweiten Teil, der gefüllt wird, wenn Der besitzende Thread läuft. Einverstanden?
FaceBro
2
@nextTide Es gibt keine zwei Teile. Die Stapel werden geteilt, Punkt. Jeder Thread hat einen eigenen Stapel, aber sie werden auch gemeinsam genutzt. Vielleicht ist eine gute Analogie, wenn Sie und Ihre Frau jeweils ein Auto haben, Sie aber jederzeit die Autos des anderen benutzen können.
David Schwartz
5

Threads teilen Daten und Code, Prozesse hingegen nicht. Der Stapel wird nicht für beide freigegeben.

Prozesse können auch Speicher gemeinsam nutzen, genauer gesagt Code, beispielsweise nach a Fork(). Dies ist jedoch ein Implementierungsdetail und eine (Betriebssystem-) Optimierung. Code, der von mehreren Prozessen gemeinsam genutzt wird, wird (hoffentlich) beim ersten Schreiben in den Code dupliziert - dies wird als Copy-on-Write bezeichnet . Ich bin mir nicht sicher über die genaue Semantik für den Code von Threads, aber ich gehe von gemeinsam genutztem Code aus.

           Thread verarbeiten

   Stapel privat privat
   Daten privat geteilt
   Code privat 1   geteilt 2

1 Der Code ist logisch privat, kann jedoch aus Leistungsgründen freigegeben werden. 2 Ich bin nicht 100% sicher.

Daniel Brückner
quelle
Ich würde sagen, dass Codesegmente (Textsegmente) im Gegensatz zu Daten auf den meisten Architekturen fast immer schreibgeschützt sind.
Jorge Córdoba
4

Threads teilen alles [1]. Es gibt einen Adressraum für den gesamten Prozess.

Jeder Thread hat seinen eigenen Stapel und seine eigenen Register, aber alle Stapel der Threads sind im gemeinsam genutzten Adressraum sichtbar.

Wenn ein Thread ein Objekt auf seinem Stapel zuweist und die Adresse an einen anderen Thread sendet, haben beide den gleichen Zugriff auf dieses Objekt.


Eigentlich ist mir gerade ein größeres Problem aufgefallen: Ich denke, Sie verwechseln zwei Verwendungen des Wortsegments .

Das Dateiformat für eine ausführbare Datei (z. B. ELF) enthält unterschiedliche Abschnitte, die als Segmente bezeichnet werden können und kompilierten Code (Text), initialisierte Daten, Linkersymbole, Debug-Informationen usw. enthalten. Es gibt keine Heap- oder Stapelsegmente hier, da dies nur Laufzeitkonstrukte sind.

Diese binären Dateisegmente können separat mit unterschiedlichen Berechtigungen in den Prozessadressraum abgebildet werden (z. B. schreibgeschützt ausführbar für Code / Text und nicht ausführbar kopierbar beim Schreiben für initialisierte Daten).

Bereiche dieses Adressraums werden gemäß Konvention (von Ihren Sprachlaufzeitbibliotheken erzwungen) für verschiedene Zwecke verwendet, z. B. Heap-Zuweisung und Thread-Stapel. Es ist jedoch alles nur Speicher und wahrscheinlich nicht segmentiert, es sei denn, Sie arbeiten im virtuellen 8086-Modus. Der Stapel jedes Threads ist ein Speicherblock, der zum Zeitpunkt der Thread-Erstellung zugewiesen wird, wobei die aktuelle Stapeloberadresse in einem Stapelzeigerregister gespeichert ist und jeder Thread seinen eigenen Stapelzeiger zusammen mit seinen anderen Registern behält.


[1] OK, ich weiß: Signalmasken, TSS / TSD usw. Der Adressraum einschließlich aller zugeordneten Programmsegmente wird jedoch weiterhin gemeinsam genutzt.

Nutzlos
quelle
3

In einem x86-Framework kann man so viele Segmente teilen (bis zu 2 ^ 16-1). Die ASM-Direktiven SEGMENT / ENDS ermöglichen dies, und die Operatoren SEG und OFFSET ermöglichen die Initialisierung von Segmentregistern. CS: IP werden normalerweise vom Loader initialisiert, aber für DS, ES, SS ist die Anwendung für die Initialisierung verantwortlich. In vielen Umgebungen sind sogenannte "vereinfachte Segmentdefinitionen" wie .code, .data, .bss, .stack usw. zulässig, und je nach "Speichermodell" (klein, groß, kompakt usw.) initialisiert der Loader Segmentregister entsprechend. Normalerweise werden .data, .bss, .stack und andere übliche Segmente (ich habe dies seit 20 Jahren nicht mehr getan, daher erinnere ich mich nicht an alle) in einer einzigen Gruppe zusammengefasst - deshalb zeigen normalerweise DS, ES und SS auf die gleicher Bereich, aber dies dient nur der Vereinfachung.

Im Allgemeinen können alle Segmentregister zur Laufzeit unterschiedliche Werte haben. Die Interviewfrage war also richtig: Welche der CODE, DATA und STACK werden von den Threads gemeinsam genutzt. Heap-Management ist etwas anderes - es ist einfach eine Folge von Aufrufen des Betriebssystems. Aber was ist, wenn Sie überhaupt kein Betriebssystem haben, wie in einem eingebetteten System - können Sie immer noch Neues / Löschen in Ihrem Code haben?

Mein Rat an die jungen Leute - lesen Sie ein gutes Assembler-Programmierbuch. Es scheint, dass die Lehrpläne der Universitäten in dieser Hinsicht ziemlich schlecht sind.

George
quelle
2

Neben dem globalen Speicher teilen Threads auch eine Reihe anderer Attribute (dh diese Attribute sind für einen Prozess global und nicht für einen Thread spezifisch). Diese Attribute umfassen Folgendes:

  • Prozess-ID und übergeordnete Prozess-ID;
  • Prozessgruppen-ID und Sitzungs-ID;
  • Steuerterminal;
  • Anmeldeinformationen verarbeiten (Benutzer- und Gruppen-IDs);
  • Dateideskriptoren öffnen;
  • Datensatzsperren erstellt mit fcntl();
  • Signal Dispositionen;
  • dateisystembezogene Informationen: umask, aktuelles Arbeitsverzeichnis und Stammverzeichnis;
  • Intervall-Timer ( setitimer()) und POSIX-Timer ( timer_create());
  • System V Semaphor rückgängig machen ( semadj) Werte (Abschnitt 47.8);
  • Ressourcenlimits;
  • CPU-Zeit verbraucht (wie von zurückgegeben times());
  • verbrauchte Ressourcen (wie von zurückgegeben getrusage()); und
  • schöner Wert (gesetzt von setpriority()und nice()).

Zu den Attributen, die für jeden Thread unterschiedlich sind, gehören die folgenden:

  • Thread-ID (Abschnitt 29.5);
  • Signalmaske;
  • threadspezifische Daten (Abschnitt 31.3);
  • alternativer Signalstapel ( sigaltstack());
  • die errno-Variable;
  • Gleitkommaumgebung (siehe fenv(3));
  • Richtlinien und Prioritäten für die Echtzeitplanung (Abschnitte 35.2 und 35.3);
  • CPU-Affinität (Linux-spezifisch, beschrieben in Abschnitt 35.4);
  • Funktionen (Linux-spezifisch, beschrieben in Kapitel 39); und
  • Stapel (lokale Variablen und Funktionsaufruf-Verknüpfungsinformationen).

Auszug aus: Die Linux-Programmierschnittstelle: Ein Handbuch zur Linux- und UNIX-Systemprogrammierung, Michael Kerrisk , Seite 619

snr
quelle
0

Thread teilen den Heap (es gibt eine Untersuchung über threadspezifische Heap), aber die aktuelle Implementierung teilt den Heap. (und natürlich den Code)

Dani
quelle
0

Während des Prozesses teilen sich alle Threads Systemressourcen wie Heapspeicher usw., während Thread einen eigenen Stapel hat

Ihre Ans sollte also Heap-Speicher sein, den alle Threads für einen Prozess gemeinsam nutzen.

Roshni
quelle