Welche Ressourcen werden zwischen Threads geteilt?

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

Threads teilen sich den gleichen Speicher, processe nicht. Nachdem ich das beantwortet hatte, schenkte mir der Interviewer ein böses Lächeln und feuerte folgende Fragen an mich:

Q. Kennen Sie die Segmente, in denen ein Programm geteilt wird?

Meine Antwort: yep (dachte, es sei einfach) Stack, Data, Code, Heap

Q. Also, sagen Sie mir: Welche Segmente teilen sich Threads?

Ich konnte das nicht beantworten und endete damit, sie alle zu sagen.

Kann jemand die richtigen und eindrucksvollen Antworten für den Unterschied zwischen einem process und einem Thread präsentieren?

   

Sie sind ziemlich korrekt, aber Threads teilen alle Segmente mit Ausnahme des Stapels. Threads haben unabhängige Callstacks, aber der Speicher in anderen Thread-Stacks ist immer noch zugänglich und theoretisch könnte man einen pointers auf Speicher im lokalen Stack-Frame eines anderen Threads halten (obwohl Sie wahrscheinlich einen besseren Platz finden sollten, um diesen Speicher zu platzieren!).

Aus Wikipedia (ich denke, das würde dem Interviewer eine wirklich gute Antwort geben: P)

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

  • processe sind typischerweise unabhängig, während Threads als Teilmengen eines processes existieren
  • processe tragen beträchtliche Zustandsinformationen, während mehrere Threads innerhalb eines processes sowohl den Zustand als auch den Speicher und andere Ressourcen teilen
  • processe haben separate Adressräume, während Threads ihren Adressraum teilen
  • processe interagieren nur über systemseitig bereitgestellte Interprozess-Kommunikationsmechanismen.
  • Der Kontextwechsel zwischen Threads im selben process ist in der Regel schneller als der Kontextwechsel zwischen processen.

Etwas, das wirklich hervorgehoben werden muss, ist dass es wirklich zwei Aspekte gibt – den theoretischen Aspekt und den Implementierungsaspekt.

Betrachten wir zuerst den theoretischen Aspekt. Sie müssen verstehen, was ein process ist, um den Unterschied zwischen einem process und einem Thread und dem, was zwischen ihnen geteilt wird, zu verstehen.

Wir haben das folgende aus Abschnitt 2.2.2 Das klassische Thread-Modell in modernen Betriebssystemen 3e von Tanenbaum:

Das processmodell basiert auf zwei unabhängigen Konzepten: Ressourcengruppierung und -ausführung. Manchmal ist es nützlich, sie zu trennen. Hier kommen Fäden rein ….

Er fährt fort:

Eine Möglichkeit, einen process zu betrachten, ist, dass damit verwandte Ressourcen zusammen gruppiert werden können. Ein process hat einen Adressraum, der Programmtext und Daten sowie andere Ressourcen enthält. Diese Ressource kann offene Dateien, untergeordnete processe, ausstehende Alarme, Signalbehandlungsroutinen, Abrechnungsinformationen und mehr enthalten. Indem sie sie in Form eines processes zusammenfügen, können sie leichter verwaltet werden. Das andere Konzept, das ein process hat, ist ein Thread der Ausführung, normalerweise verkürzt auf nur Thread. Der Thread hat einen Programmzähler, der verfolgt, welche statement als nächstes ausgeführt werden soll. Es hat Register, die seine aktuellen Arbeitsvariablen enthalten. Es hat einen Stapel, der den Ausführungsverlauf enthält, mit einem Rahmen für jede aufgerufene Prozedur, von der aber noch nicht zurückgekehrt wurde. Obwohl ein Thread in einem process ausgeführt werden muss, sind der Thread und sein process unterschiedliche Konzepte und können separat behandelt werden. processe werden verwendet, um Ressourcen zusammenzufassen. Threads sind die Entitäten, die zur Ausführung auf der CPU geplant sind.

Weiter unten gibt er die folgende Tabelle an:

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 oben genannte ist, was Sie brauchen, damit Threads funktionieren. Wie andere darauf hingewiesen haben, sind Dinge wie Segmente Betriebssystem-abhängige Implementierungsdetails.

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

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

Die Person, die dich interviewt hat, hat eine versteckte Annahme gemacht, die er / sie nicht angegeben hat, und das ist ein blöder Trick, den man ziehen muss.

Also bezüglich

Q. Also sagen Sie mir, welches Segment Thread teilen?

Die Segmente sind für die Frage zumindest unter Windows irrelevant. Threads teilen sich den gesamten Adressraum. Es gibt nur ein Stack-Segment, SS, und es zeigt genau das gleiche an, was DS, ES und CS tun [2]. Dh der ganze blutige Userspace . 0-2GB. Das bedeutet natürlich nicht, dass Threads nur einen Stack haben. Natürlich hat jeder seinen eigenen Stack, aber x86-Segmente werden nicht für diesen Zweck 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. Aus dem ntsd notepad : cs=001b ss=0023 ds=0023 es=0023

Im Allgemeinen werden Threads leichter process genannt. Wenn wir den Speicher in drei Abschnitte unterteilen, dann wird es sein: Code, Daten und Stack. Jeder process hat seine eigenen Code-, Daten- und Stack-Abschnitte und aufgrund dieses Kontext ist die Switch-Zeit ein wenig hoch. Um die Kontextumschaltzeit zu reduzieren, haben die Leute ein Konzept des Threads entwickelt, das Daten und Code-Segmente mit anderen Threads / processen teilt und ein eigenes STACK-Segment hat.

Ein process hat Code-, Daten-, Heap- und Stack-Segmente. Der statementspointer (IP) eines Threads oder Threads verweist jetzt auf das Codesegment des processes. Die Daten- und Heapsegmente werden von allen Threads gemeinsam verwendet. Was ist nun mit dem Stapelbereich? Was ist eigentlich der Stapelbereich? Es ist ein Bereich, der durch den process nur für seinen Thread erstellt wurde, da Stacks viel schneller als Heaps usw. verwendet werden können. Der Stapelbereich des processes ist auf Threads aufgeteilt, dh wenn es 3 Threads gibt, dann wird der Stackbereich des processes aufgeteilt Stack-Bereich des processes ist in 3 Teile unterteilt und jeder ist auf die 3 Threads gegeben. Mit anderen Worten, wenn wir sagen, dass jeder Thread seinen eigenen Stapel hat, ist dieser Stapel tatsächlich ein Teil des processstapelbereichs, der jedem Thread zugewiesen ist. Wenn ein Thread seine Ausführung beendet, wird der Stack des Threads vom process zurückgewonnen. In der Tat ist nicht nur der Stapel eines processes unter Threads aufgeteilt, sondern alle Registersätze, die ein Thread verwendet, wie SP, PC und Zustandsregister, sind die Register des processes. Wenn es um das Teilen geht, werden die Code-, Daten- und Heap-Bereiche geteilt, während der Stapelbereich nur zwischen Threads aufgeteilt wird.

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

Threads teilen Daten und Code, processe dagegen nicht. Der Stapel ist nicht für beide freigegeben.

processe können auch Speicher teilen, genauer gesagt Code, zum Beispiel nach einem Fork() , aber dies ist ein Implementierungsdetail und (Betriebssystem-) Optimierung. Code, der von mehreren processen gemeinsam genutzt wird, wird (hoffentlich) beim ersten Schreiben in den Code dupliziert – dies wird als Kopieren-beim-Schreiben bezeichnet . Ich bin mir nicht sicher über die genaue Semantik für den Code von Threads, aber ich gehe von gemeinsamem Code aus.

            processfaden

    Stack privat privat
    Daten privat geteilt
    Code privat 1 geteilt 2

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

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

Jeder Thread verfügt über einen eigenen Stack und eigene Register. Die Stacks aller Threads sind jedoch im freigegebenen Adressraum sichtbar.

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


Tatsächlich habe ich ein breiteres Problem bemerkt: Ich denke, du verwechselst zwei Verwendungen des Wortsegments .

Das Dateiformat für eine ausführbare Datei (z. B. ELF) weist verschiedene Abschnitte auf, 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 diese Laufzeit-only-Konstrukte sind.

Diese Binärdateisegmente können separat in den processadressraum mit unterschiedlichen Berechtigungen abgebildet werden (z. B. schreibgeschützte ausführbare Datei für Code / Text und nicht ausführbare Kopie-für-Datei für initialisierte Daten).

Bereiche dieses Adressraums werden für verschiedene Zwecke verwendet, wie zum Beispiel Heap-Zuweisung und Thread-Stacks (durch Ihre Sprachlaufzeitbibliotheken erzwungen). Es ist jedoch nur Speicher und wahrscheinlich nicht segmentiert, es sei denn, Sie laufen im virtuellen 8086-Modus. Der Stapel jedes Threads ist ein Stapel Speicher, der zur Thread-Erzeugungszeit zugeordnet ist, wobei die aktuelle Stapelanfangsadresse 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 geteilt.

In einem x86-Framework kann man so viele Segmente teilen (bis zu 2 ^ 16-1). Die ASM-statementen SEGMENT / ENDS erlauben dies, und die Operatoren SEG und OFFSET erlauben die Initialisierung von Segmentregistern. CS: IP werden normalerweise vom Loader initialisiert, aber für DS, ES, SS ist die Anwendung mit der Initialisierung verantwortlich. Viele Umgebungen erlauben die sogenannten “vereinfachten Segmentdefinitionen” wie .code, .data, .bss, .stack usw. und, abhängig vom “Speichermodell” (klein, groß, kompakt usw.) initialisiert der Loader Segmentregister entsprechend. Normalerweise .data, .bss, .stack und andere übliche Segmente (ich habe das seit 20 Jahren nicht mehr gemacht, so dass ich mich nicht an alle erinnere) sind in einer einzigen Gruppe zusammengefasst – deshalb weisen normalerweise DS, ES und SS darauf hin der gleiche Bereich, aber das ist nur um die Dinge zu vereinfachen.

Im Allgemeinen können alle Segmentregister zur Laufzeit unterschiedliche Werte haben. Die Interviewfrage war also richtig: Welche von CODE, DATA und STACK werden zwischen Threads geteilt? Heap-Management ist etwas anderes – es ist einfach eine Abfolge von Aufrufen an das Betriebssystem. Aber was, wenn Sie überhaupt kein Betriebssystem haben, wie in einem Embedded-System – können Sie in Ihrem Code noch neue / löschen?

Mein Rat an die Jugendlichen – lies ein gutes assemblybuch. Es scheint, dass die Lehrpläne der Universitäten in dieser Hinsicht ziemlich schlecht sind.

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

Im process teilen sich alle Threads Systemressourcen wie Heap Memory usw. während Thread einen eigenen Stack hat

Daher sollte Ihr ANS Heap-Speicher sein, den alle Threads für einen process freigeben.