Vorlesung Betriebssysteme I
Thema 8: Threads (Aktivitäten, die zweite)
Robert Baumgartl
13. Januar 2020
1 / 22
Threads (Leichtgewichtsprozesse)
I Prozess = Container für Ressourcen + (ein)
identifizierbarer Handlungsablauf (“Thread of Execution”) I Idee: Trennung beider Konzepte:
I Prozess = Container für Ressourcen (passiv)
I Thread = identifizierbarer unabhängiger Handlungsablauf (aktiv)
I →ein oder mehrere Threads pro Prozess möglich, diese teilen sich die Ressourcen des Prozesses
I →parallele Abarbeitung innerhalb eines Adressraums Der Begriff „Thread“ ist auch im Deutschen weitestgehend etabliert, die korrekte Übersetzung „Faden“ benutzen nur Fanatiker. Eher wird noch „Leichtgewichtsprozess“ eingesetzt.
2 / 22
Veranschaulichung mehrerer Threads
...... ......
......
P1
......
P3
......
P2 P1
......
Abbildung:Übergang von mehreren Prozessen zu mehreren Threads in einem Prozess
3 / 22
Konsequenzen
I Thread kann nur innerhalb eines Prozesses existieren!
I alle Threads eines Prozesses teilen sich dessen
Ressourcen und Adressraum bis auf die Register und den Stack
I ⇒kein Schutz zwischen Threads eines Prozesses I Kooperation im Vordergrund
dem Prozess gehörend jedem Thread gehörend Adressraum Program Counter Globale Variablen Register
eröffnete Dateien Stack (!)
Signale automatische Variablen Kindprozesse Globalzustand
Tabelle:Gemeinsame und private Ressourcen von Threads eines Prozesses (Beispiele)
4 / 22
Warum Threads?
I feingranulare Parallelität der Verarbeitung
I Erzeugung/Vernichtung viel schneller als von Prozessen I effektive Ausnutzung mehrerer CPUs/Kerne
I Umschaltung zwischen Threads eines Prozesses schnell I Dekomposition in Threads liefert Performancegewinn,
insbesondere wenn Ein-/Ausgabe-beschränkte Funktionalität
Abbildung:Textverarbeitung mit 3 Threads (Tanenbaum:Modern Operating Systems, S.95)
5 / 22
Variante 1: User-Level-Threads
I Threads werden im User Mode (d.h., ohne Intervention und Wissen des Betriebssystems) erzeugt, synchronisiert, vernichtet
I Threadbibliothek, die Routinen zum Erzeugen, Beenden, Synchronisieren und Umschalten von Threads realisiert, erforderlich
I Threadbibliothek übernimmt Management aller Threads (speichert deren Zustandsinformationen)
I Umschaltungvorgang: Sichern und Restaurieren des Thread-Kontextes (alle Register)
I kooperatives Programmiermodell: jeder Thread muss freiwillig (ab und zu) den Prozessor abgeben
I Kernel hat kein Wissen über Threads, kennt (und verwaltet) nur Prozesse
I m:1-Abbildung, d. h. , allen Threads ist genau eine Kernel-Aktivität zugeordnet, nämlich der zugehörige Prozess
6 / 22
Veranschaulichung von User-Level-Threads
...... ...... ......
......
P1
Thread Library
T1 T2 T3
User Mode Kernel Mode
Abbildung:User-Level-Threads
7 / 22
User-Level-Threads, cont’d
Bewertung:
+ kann (auch) für Betriebssystem implementiert werden, welches kein Threadkonzept kennt (z. B. MS-DOS) + Threadoperationen besonders schnell, da kein
Kernein-/austritt, kein Flush der Prozessorcaches, kein Adressraumwechsel
– blockierender Systemruf blockiertalleThreads eines Prozesses (verhindert Parallelität), Abhilfe:
? nichtblockierende Systemrufe
? im Vorhinein ermitteln, ob Ruf blockieren wird (z. B. mittels select()); wenn ja, dann Weiterarbeit eines anderen Threads
– Page Faultblockiert alle Threads eines Prozesses – kooperatives Programmiermodell mit freiwilliger Abgabe
des Prozessors erforderlich (da keine unterbrechende Instanz aka Betriebssystem)
8 / 22
Variante 2: Kernel-Level-Threads
I 1:1-Abbildung (jedem Thread istgenau eineAktivität des Kernels zugeordnet)
I Threads im Kernel verwaltet, dieser verteilt Threads auf alle existierenden Kerne
I Threadoperationen teurer, da mit Systemruf (~eintritt,
~austritt) verbunden (z. B.CreateThread()in Win32) I Kombinationen aus User-Level- und Kernel-Level-Threads
sind ebenfalls möglich
9 / 22
Veranschaulichung von Kernel-Level-Threads
...... ............ ......
P1
CPU
T1 T2 T3
User Mode Kernel Mode
Abbildung:Kernel-Level-Threads
10 / 22
Transition single-threaded → multi-threaded
Komplexität der Umwandlung traditioneller sequentieller Programme inmultithreaded applicationsdarf nicht unterschätzt werden!
Einige Problemkreise:
I globale Systemvariablen, wieerrnostehen nur einmal pro Prozess zur Verfügung
I nicht reentranter Code, z. B. in Bibliotheken, muss vor gleichzeitigem Betreten durch mehrere Threads geschützt werden
I Signale sind meist prozessspezifisch
I Management mehrerer Stacks bei User-Level-Threads
11 / 22
Threads in Windows
I Windows-Welt: beginnend ab Windows NT (Win32-API) sind Kernel-Level-Threads im System verankert.
I CreateProcess()legt einen neuen Adressraum an und startet in ihm einen (ersten) Thread
I mittelsCreateThread()können in einem existierenden Prozess weitere Threads gestartet werden
I User-Level-Threads existierenzusätzlich:die so genanntenFibers
I d. h. , es können mehrere User-Level-Threads (Fibers) in einem Kernel-Level-Thread existieren
12 / 22
Threads in Linux
I ab Kernel 2.0 BibliothekLinuxThreads
I weitgehend, aber nicht vollständig, POSIX-kompatibel I limitiert (max. Threadanzahl 8192)
I nicht allzu performant
I daher ab 2002 Entwicklung derNative POSIX Thread Library(NPTL)
I behebt diese Nachteile, voll POSIX-kompatibel I 1:1-Implementierung (1 Thread pro Kernelaktivität) I andere Unixe besitzen u. U. andere
Thread-Implementierungen, die aber stets POSIX-Threads realisieren
Abfrage der installierten Thread-Version in Linux:
~> getconf GNU_LIBPTHREAD_VERSION
13 / 22
POSIX-Threads (Pthreads)
I POSIX standardisiert ein Thread-API für Unix-artige Betriebssysteme
I API wirdPthreadsgenannt
I Standard enthält keine Aussage zur Implementierung der Funktionen (z. B., ob Kernel- oder User-Level-Threads realisiert werden sollen)
I man 7 pthreads
I auch für Win32 verfügbar (pthreads-w32)
14 / 22
Pthreads-API – Übersicht
pthread_create() Anlegen eines neuen Threads pthread_join() Warten auf Ende des Threads pthread_exit() Beenden des rufenden Threads pthread_detach() Abkoppeln vom Vater
pthread_kill() Zustellung eines Signals an Thread pthread_attr_init() Init der Thread-Attribute
pthread_mutex_lock() pthread_mutex_unlock()
Synchronisation am Mutex
pthread_cond_init() Anlegen einer Bedingungsvariable pthread_cond_wait()
pthread_cond_signal()
Sync. an Bedingungsvariable Tabelle:Einige Funktionen der Pthreads-API
15 / 22
Pthreads-Programmierung
I #include <pthread.h>
I Linken mit Schalter-lpthread
I Funktionsnamen beginnen stets mitpthread_
I alle Funktionen liefern 0 wenn erfolgreich, -1 bei Fehler I Threads eines Prozesses kommunizieren über
gemeinsame (globale) Variable I IPC nur zu Threads anderer Prozesse
16 / 22
Hello, world mittels Pthreads
# i n c l u d e < p t h r e a d . h>
# i n c l u d e < s t d i o . h>
# i n c l u d e < s t d l i b . h>
# d e f i n e MAXITER 200000 v o i d ∗t h r e a d _ f (v o i d ∗arg ) {
p r i n t f ("world!\n") ; p t h r e a d _ e x i t ( NULL ) ; }
i n t main (v o i d) {
p t h r e a d _ t a ; i n t r e t ;
p r i n t f ("Hello, ") ; r e t = p t h r e a d _ c r e a t e (
&a , /∗ p o i n t e r t o v a r i a b l e c o n t a i n i n g t h r e a d ID ∗/
NULL , /∗ p o i n t e r t o t h r e a d a t t r i b u t e s ∗/
(v o i d∗) &t h r e a d _ f , /∗ t h r e a d f u n c t i o n ∗/
NULL ) ; /∗ p o i n t e r t o argument ∗/
i f ( r e t ! = 0 ) {
p e r r o r ("creating 1st thread") ; e x i t ( EXIT_FAILURE ) ;
}
p t h r e a d _ j o i n ( a , NULL ) ; e x i t ( EXIT_SUCCESS ) ; }
17 / 22
Erzeugung eines Threads
int pthread_create(pthread_t *tid, pthread_attr_t
*attr, void* (*start_fkt)(void*), void *arg);
I erzeugt neuen Thread
I *tid: Identifikator des neuen Threads I *attr: legt Attribute des Threads fest I *start\_fkt: Startfunktion
I *arg: Zeiger auf (beliebiges) Datum, welches der Thread als Parameter erhält
I keine Vater-Sohn-Beziehungen; jeder darf create-n und join-en
18 / 22
Join und Detach
int pthread_join(pthread_t tid, void **ret);
I Analogon zum waitpid(), wartet auf Ende des Threads mit Id tid
I Rückgabewert über ret
I erst nach join() wird der Thread vernichtet (analog Zombie-Status)
int pthread_detach(pthread_t tid);
I macht den Thread un-join-bar, d.h., er wird sofort vernichtet, wenn er seine Startfunktion verlässt
19 / 22
GNU Portable Threads (GNU Pth)
I User-Level-Threads für Unix I m:1-Implementierung
I http://www.gnu.org/software/pth/
I kooperatives Multitasking mit Prioritäten I Linken mit Schalter-lpth
20 / 22
Hello, world! mit GNU Pth
# i n c l u d e < p t h . h>
# i n c l u d e < s t d i o . h>
# i n c l u d e < s t d l i b . h>
v o i d ∗p t h _ t h r e a d (v o i d ∗arg ) {
p r i n t f ("world!\n") ; p t h _ e x i t ( NULL ) ; }
i n t main (v o i d) {
p t h _ t t i d ; i f ( ! p t h _ i n i t ( ) ) {
p r i n t f ("No pth lib available\n") ; e x i t ( EXIT_FAILURE ) ;
}
p r i n t f ("Hello, ") ;
t i d = pth_spawn ( PTH_ATTR_DEFAULT, &p t h _ t h r e a d , NULL ) ; i f ( ! t i d ) {
p r i n t f ("Could not spawn thread\n") ; e x i t ( EXIT_FAILURE ) ;
}
p t h _ j o i n ( t i d , NULL ) ; r e t u r n 0 ;
}
21 / 22
Was haben wir gelernt?
I Threads sind eine Abstraktion zur effizienten Parallelisierung von Programmabläufen.
I Kernel-Level-Threads↔User-Level-Threads I Windows bringt beide Arten „von Haus aus“ mit
I wichtigste Implementierung unter Unix/Linux: Pthreads I pthread_create()startet einen neuen Thread
I pthread_exit()beendet einen Thread (oder: Verlassen der Threadfunktion)
22 / 22