In dieser Übung geben wir euch eine kurze Einführung darüber, was Threads sind, wie
man diese verwendet und wie man diese untereinander koordiniert.
Bevor wir nun mit Threads anfangen, wollen wir noch einmal kurz rekapitulieren, was
ein Prozess ist bzw. was das Unix-Prozesskonzept ist.
Ein Unix-Prozess ist im Wesentlichen eine Ausführungsumgebung für einen einzelnen Aktivitätsträger,
wobei ein Aktivitätsträger jeweils eine Folge von Instruktionen ausführen kann.
Dieses Konzept hat viele Jahre lang recht gut funktioniert, aber inzwischen ist es an seine Grenzen gestoßen.
Das liegt vor allem daran, dass man damit keine parallelen Abläufe innerhalb eines einzelnen logischen Adressraums abbilden kann.
Und das hat zur Folge, dass ein einzelnes Programm mit einem einzigen Adressraum nicht echt parallel auf einem Multiprozessorsystem ausgeführt werden kann.
Man kann sich zwar ein bisschen behelfen, indem man nun mit der zuletzt vorgestellten Fork-Operation einen neuen Prozess startet und damit eine Art Server-Client-Modell implementiert.
Jedoch ist diese Möglichkeit oft ein bisschen zu schwergewichtig, da sie unnötig viele Systemressourcen benötigt.
Darüber hinaus ist auch die Kommunikation zwischen dem entstehenden Server und Client etwas umständlich.
Was man stattdessen eigentlich möchte, ist, dass man innerhalb eines gemeinsamen Adressraums mehrere Aktivitätsträger hat, damit man quasi parallel an einem Ergebnis arbeiten kann.
Was man also machen muss, ist, das Unix-Prozesskonzept so weit aufzubohren, dass man mehrere Aktivitätsträger innerhalb eines einzelnen Prozesses abbilden kann.
Und damit wären wir schon bei den sogenannten Threads.
Ein Thread ist ein Aktivitätsträger, der innerhalb eines geteilten Adressraums läuft.
Dabei kann man noch mal weiter unterscheiden, zum einen zwischen den federgewichtigen Threads oder auch genannt User-Threads, die rein durch die Anwendungsebene realisiert sind.
Das sieht in der Praxis zum Beispiel so aus, dass man eine Bibliothek in sein Programm mit einbindet, die eben solche federgewichtigen Threads und alle Primitiven, die man zur Steuerung braucht, implementiert.
Die Konsequenz daraus, dass diese Threads rein in der Anwendungsebene implementiert sind, ist, dass der Kernel keinerlei Wissen über diese Threads hat, nicht einmal über deren Existenz.
Indies wiederum hat zur Folge, dass die Erzeugung von solchen Threads extrem günstig ist, da hierbei nicht der Kernel mit involviert werden muss und damit kein Kontextwechsel zwischen dem Adressraum der Anwendung und dem des Kernels geschieht.
Der Nachteil, der sich daraus jedoch ergibt, ist, dass Anwendungen, die rein mit federgewichtigen Prozessen implementiert sind, Multiprozessorsysteme nicht parallel nutzen können.
Das liegt daran, dass der Kernel die Verantwortung dafür trägt, einen Prozess einem Prozess zuzuordnen.
Der Kernel sieht an der Stelle jedoch nur einen einzelnen Prozess und weiß nicht, dass dieser wiederum in mehrere federgewichtige unterteilt ist.
Dies hat auch Auswirkungen auf das Scheduling zwischen den einzelnen Threads, da dies nun ohne Unterstützung des Betriebssystems geschieht.
Das hat zur Folge, dass der Scheduler des Betriebssystems Entscheidungen treffen kann, die für die darüberliegende Implementierung der federgewichtigen Threads ungünstig ist.
Im Gegensatz dazu gibt es noch die leichtgewichtigen Prozesse, auch genannt Kernel Threads.
Unter einem Kernel Thread versteht man nun einen eigenen Aktivitätsträger, der dem Betriebssystem Kern bekannt ist und der als Teil einer Gruppe von Threads eben auf den gemeinsamen Betriebsmitteln eines Prozesses arbeiten kann.
Dazu gehört auch der Adressraum, der zwischen allen Threads eines Prozesses geteilt wird.
Der Nachteil, der mit solchen Kernel Threads eben kommt, ist, dass die Erzeugung eines solchen Threads relativ teuer ist, da hier die Erzeugung nicht mehr durch die Anwendungsebene, sondern eben durch den Kernel realisiert wird.
Nun wollen wir nochmal die verschiedenen Arten von Threads gegeneinander überstellen und uns anhand einer Grafik verdeutlichen, wie das denn aussieht.
Zum Vergleich haben wir hier noch einmal die schwergewichtigen Prozesse, die eben entstehen, wenn der Forksystemaufruf aufgerufen wird.
In der Grafik ist jedes der Kästchen ein eigener Adressraum.
Oberhalb der gestrichelten Linie ist der Kerneladressraum und unterhalb der gestrichelten Linie sind die je verschiedenen Benutzeradressräume.
Hierbei gibt es nur einen Kerneladressraum, wobei für jeden Prozess ein eigener Benutzeradressraum erstellt wird.
Und die Eigenschaft der schwergewichtigen Prozesse ist nun, dass mit jedem dieser Benutzeradressräume je ein eigener Kontrollfluss assoziiert ist.
Die Adressräume dienen als Abschottung unter den Prozessen, denn keiner kann in den Adressraum des jeweils anderen zugreifen.
Die Alternative dazu sind nun die Kerneladressthreads.
Hier sieht es so aus, dass man innerhalb eines gemeinsamen Benutzeradressraums mehrere Aktivitätsträger hat.
Jeder dieser Aktivitätsträger kann mehr oder weniger unabhängig arbeiten und auf dieselben Daten zugreifen wie der Rest.
Hierbei ist zu beachten, dass sie sich jedoch untereinander koordinieren wollen, wenn sie gleichzeitig auf eben die geteilten Daten zugreifen wollen.
Für sich privat haben sie jedoch jeweils einen eigenen Stack.
Die einzelnen Aktivitätsträger werden hierbei durch den Kernel verwaltet.
Nämlich weiß der Scheduler den einzelnen Threads jeweils die CPU für eine kurze Zeit zu.
Und für den Fall, dass man in einem Computer mehrere Kerne hat, ist es sogar möglich, dass der Scheduler mehrere Threads des gleichen Prozesses gleichzeitig einplant, sodass diese echt parallel laufen können.
Das Konzept der Kernelthreads ist also eine 1 zu 1 Abbildung eines Aktivitätsträgers, wie er vom Kernel verwaltet wird.
Nun können wir nochmal den Vergleich zu den federgewichtigen Threads wagen.
Hier hat man ebenfalls einen Adressraum, der einem Prozess zugeordnet ist.
Aber innerhalb dieses einen Adressraums gibt es nur einen Aktivitätsträger, der durch den Kernel verwaltet wird.
Nun wird innerhalb des Prozesses eine eigene Bibliothek verwendet, die zum Beispiel diese federgewichtigen Threads zur Verfügung stellt.
Diese Bibliothek muss zum Beispiel einen eigenen Scheduler mitbringen, der die Entscheidung trifft, wann welcher der federgewichtigen Threads zur Ausführung kommt.
Zugänglich über
Offener Zugang
Dauer
00:18:05 Min
Aufnahmedatum
2020-07-06
Hochgeladen am
2020-07-06 12:36:30
Sprache
de-DE