Pthreads. David Klaftenegger. Seminar: Multicore Programmierung Sommersemester

Ähnliche Dokumente
Pthreads. David Klaftenegger. Seminar: Multicore Programmierung Sommersemester

PThreads. Pthreads. Jeder Hersteller hatte eine eigene Implementierung von Threads oder light weight processes

Threads Einführung. Zustände von Threads

Softwaresysteme I Übungen Jürgen Kleinöder Universität Erlangen-Nürnberg Informatik 4, 2007 U9.fm

U9-3 Vergleich von Thread-Konzepten. U9-2 Motivation von Threads. U9-3 Vergleich von Thread-Konzepten (2) U9-1 Überblick

POSIX-Threads. Aufgabe 9 SP - Ü U10.1

I 7. Übung. I-1 Überblick. Besprechung Aufgabe 5 (mysh) Online-Evaluation. Posix Threads. Ü SoS I I.1

Vorlesung Betriebssysteme I

U8 POSIX-Threads U8 POSIX-Threads

U8-1 Motivation von Threads. U8-2 Vergleich von Thread-Konzepten. U8-2 Vergleich von Thread-Konzepten (2) Motivation

Shared-Memory Programmiermodelle

Tafelübung zu BS 2. Threadsynchronisation

Systeme I: Betriebssysteme Kapitel 4 Prozesse. Wolfram Burgard

OpenMP - Threading- Spracherweiterung für C/C++ Matthias Klein, Michael Pötz Systemprogrammierung 15. Juni 2009

Threads. Foliensatz 8: Threads Folie 1. Hans-Georg Eßer, TH Nürnberg Systemprogrammierung, Sommersemester 2015

Sequentielle Programm- / Funktionsausführung innerhalb eines Prozesses ( thread = Ausführungsfaden )

Markus Klußmann, Amjad Saadeh Institut für Informatik. Pthreads. von Markus Klußmann und Amjad Saadeh. Pthreads

Master-Thread führt Programm aus, bis durch die Direktive

Threads. Netzwerk - Programmierung. Alexander Sczyrba Jan Krüger

Homogene Multi-Core-Prozessor-Architekturen

e) Welche Aussage zu Speicherzuteilungsverfahren ist falsch?

Testen nebenläufiger Objekte

Lehrstuhl für Datenverarbeitung. Technische Universität München. Leistungskurs C++ Multithreading

Zeiger vom Typ void* benötigen weniger Speicher als andere Zeiger, da bei anderen Zeigertypen zusätzlich die Größe gespeichert werden muss.

Nebenläufige Programmierung in Java: Threads

Lehrstuhl für Datenverarbeitung. Technische Universität München. Leistungskurs C++ Multithreading

Systeme I: Betriebssysteme Kapitel 4 Prozesse. Maren Bennewitz

Einige Grundlagen zu OpenMP

RTOS Einführung. Version: Datum: Autor: Werner Dichler

Lehrstuhl für Datenverarbeitung. Technische Universität München. Leistungskurs C++ Multithreading

Early first draft Höllische Programmiersprachen Seminar im WS 2014/15 Speichermanagement

Fakultät für Informatik der Technischen Universität München. Kapitel 3. Nebenläufigkeit

Praxisorientierte Einführung in C++ Lektion: "Virtuelle Methoden"

Aufgabenblatt 6 Musterlösung

Objekte. Theorieteil. Inhaltsverzeichnis. Begriffe. Programmieren mit Java Modul 5. 1 Modulübersicht 3

Besprechung Aufgabe 1: Prozesse verwalten Fortsetzung Grundlagen C-Programmierung Aufgabe 2: Threadsynchronisation

Systeme I: Betriebssysteme Kapitel 4 Prozesse. Maren Bennewitz

Übungen zu Systemnahe Programmierung in C (SPiC) Sommersemester 2018

Nicht-blockierende Synchronisation für Echtzeitsysteme

Inhalt. Übungen zu Systemnahe Programmierung in C (SPiC) Inhalt. Motivation

Übungen zu Systemnahe Programmierung in C (SPiC)

Übungen zu Systemprogrammierung 1 (SP1)

Übungen zu Systemnahe Programmierung in C (SPiC) Inhalt. Sebastian Maier (Lehrstuhl Informatik 4) Übung 10. Sommersemester 2016

Betriebssysteme. Tafelübung 2. Thread-Synchronisation. Olaf Spinczyk.

Prinzipien der objektorientierten Programmierung (OOP)

Echtzeitbetriebssysteme (am Beispiel QNX) Dr. Stefan Enderle HS Esslingen

Organisatorisches. Folien (u.a.) gibt's auf der Lva-Homepage zum Download

Laborskript Verteilte Systeme

Was machen wir heute? Betriebssysteme Tutorium 3. Organisatorisches. Prozesskontrollblock (PCB) Programmieraufgaben. Frage 3.1.a

Shared-Memory Programmiermodelle

Javakurs für Fortgeschrittene

Alle Fäden in der Hand

Repetitorium Programmieren I + II

Programmiertechnik. Teil 4. C++ Funktionen: Prototypen Overloading Parameter. C++ Funktionen: Eigenschaften

Programmierkurs. Steffen Müthing. January 18, Interdisciplinary Center for Scientific Computing, Heidelberg University

Dr. Monika Meiler. Inhalt

parallele Prozesse auf sequenziellen Prozessoren Ein Process ist ein typisches Programm, mit eigenem Addressraum im Speicher.

Windows 2000 Scheduler

Betriebssysteme. Vorlesung im Herbstsemester 2010 Universität Mannheim. Kapitel 6: Speicherbasierte Prozessinteraktion

Prozessrechner-Praktikum Echtzeitsysteme

Tafelübung zu BS 2. Threadsynchronisation

1 pulsierender Speicher

Java Concurrency Utilities

Prozesse. Prozesse sind Programme. Prozesse können aus Unterprozessen bestehen. Prozesshierarchie Unterprozesse Threads

Seminar: Multi-Core Architectures and Programming

Betriebssysteme Übung 2. Tutorium System Calls & Multiprogramming

Übung zu Grundlagen der Betriebssysteme. 10. Übung

Teil 8: Dynamische Speicherverwaltung. Prof. Dr. Herbert Fischer Fachhochschule Deggendorf Prof. Dr. Manfred Beham Fachhochschule Amberg-Weiden

Organisatorisches. Folien (u.a.) auf der Lva-Homepage Skriptum über MU Online

2Binden 3. und Bibliotheken

Unicode Support Atomic Operations Thread Support Type-Generic Makros Sicherheit Ease-of-Use C11. Thomas Duckardt

C++ Teil 7. Sven Groß. 30. Nov Sven Groß (IGPM, RWTH Aachen) C++ Teil Nov / 13

Betriebssysteme. G: Parallele Prozesse. (Teil B: Klassische Problemstellungen, Mutual Exclusion, kritische Regionen)

Tafelübung zu BS 2. Threadsynchronisation

HSR Rapperswil 2001 Markus Rigling. Programmieren: Smart Pointer Auflage

(Ausnahmebehandlung)

Übung zur Vorlesung Wissenschaftliches Rechnen Sommersemester 2012 Auffrischung zur Programmierung in C++, 2. Teil

Software Entwicklung 1

Objektorientierte Programmierung

Kapitel 9. Ausnahmebehandlung in Java. Skript zur Vorlesung Einführung in die Programmierung

Aufgabe 9: recgrep rekursive, parallele Suche (letzte Aufgabe)

OpenCL. Programmiersprachen im Multicore-Zeitalter. Tim Wiersdörfer

Prozesse und Threads. wissen leben WWU Münster

Praktikum aus Softwareentwicklung 2, Stunde 5

OpenMP. Viktor Styrbul

Übungen zu Systemprogrammierung 1

Repetitorium Programmieren I + II

K Ergänzungen zur Einführung in C

Wirtschaftsinformatik II Sommersemester Lo sungshinweise zu den Ü bungen P. Mandl, M. Dolag, B. Rottmüller, et al.

Programmierung mit C Zeiger

Praktische Informatik 1

C++ Teil 6. Sven Groß. 27. Mai Sven Groß (IGPM, RWTH Aachen) C++ Teil Mai / 14

C++ Teil 5. Sven Groß. 13. Mai Sven Groß (IGPM, RWTH Aachen) C++ Teil Mai / 18

Transkript:

Pthreads David Klaftenegger Seminar: Multicore Programmierung Sommersemester 2009 16.07.2009 1

Inhaltsverzeichnis 1 Einführung 3 1.1 Pthreads.............................. 3 1.2 Speichermodell.......................... 4 2 Pthreads - API 5 2.1 Thread-Management....................... 5 2.2 Mutex............................... 7 2.3 Conditions............................. 10 2.4 Thread-Abbruch......................... 13 3 Probleme 14 3.1 Implementierungsvielfalt..................... 14 3.2 Prioritätsinversion........................ 15 4 Alternativen zu Pthreads 15 Literatur 17 2

1 Einführung Der Begriff Threads (deutsch: Fäden) bezeichnet im Allgemeinen unabhängige Ausführungsstränge eines Programms. Sie werden auch als leichtgewichtige Prozesse beschrieben, da sie Prozessen ähneln, aber mit geringerem Aufwand erzeugt werden können. Jeder Thread verfügt über einen eigenen Stack für die unabhängige Ausführung, alle anderen Statusinformationen werden mit dem übergeordneten Prozess geteilt. Um die Abarbeitung von Threads zu verteilen existieren drei Modelle: 1. 1:1 Threads werden direkt auf verteilbare Entitäten (Prozesse) des Betriebssystems abgebildet, vom Betriebssystem also wie vollwertige Prozesse verteilt 2. N:M Threads werden auf M virtuellen Prozessoren ausgeführt. Sowohl das Betriebssystem, als auch die Thread-Software-Bibliothek übernehmen einen Teil der Steuerung, welcher Thread auf welchem Prozessor ausgeführt wird. 3. N:1 Threads laufen komplett innerhalb eines Betriebssystem-Prozesses ab, weswegen auch nur ein Prozessor gleichzeitig genutzt werden kann. Es handelt sich hierbei also nicht um echte Parallelität. 1.1 Pthreads Eine häufig anzutreffende Form von Threads ist die Pthreads-API. Der Name steht für POSIX Threads, die API ist seit 1995 in IEEE Std 1003.1c standardisiert, die letzten Änderungen fanden im Jahr 2008 statt. Der Standard definiert eine C-Schnittstelle zur Verwendung von Threads, und trifft keine Aussagen über die Implementierung. Als generelles Leitbild orientiert sich Pthreads an der fork/join-parallelität wie von [Qui04] beschrieben. Obwohl im Rahmen von POSIX (Portable Operating System Interface for Unix) entwickelt existieren auch reine Software-Implementierungen [Eng06] und Implementierungen für Betriebssysteme, die nicht mit Unix kompatibel sind. Am bekanntesten ist hierbei pthreads-w32 für Windows [Joh09]. Als Thread-Verteilungsmodell wird je nach Implementierung eines der drei Modelle genutzt, wobei die N:1-Verteilung nur in Software-Implementierungen ohne Multicore-Nutzung verwendet wird. In letzter Zeit zeichnet sich eine 3

Tendenz zur Nutzung des 1:1-Modells ab, doch existieren zum Beispiel unter FreeBSD noch aktuelle Implementierungen mit dem N:M-Modell. 1.2 Speichermodell Das Pthreads zu Grunde liegende Speichermodell ist shared memory (gemeinsam genutzter Speicher), das heißt alle Threads können über den gesamten Adressraum verfügen. Abbildung 1: Speichermodell (nach [Bar09]) Als privater Speicher steht bei Pthreads der eigene Stack zur Verfügung, im gemeinsam genutzten Speicher kann jeder Thread alles lesen und schreiben, sofern er die Speicheradresse der Daten kennt. Zusätzlich stellt pthreads mit keys ein Konzept bereit, um Thread-spezifische Daten (beziehungsweise Zeiger darauf) außerhalb des Thread-Stacks abzulegen. Es liegt in der Verantwortung des Programmierers dafür zu sorgen, dass keine inkonsistenten 4

Zustände durch Schreiboperationen auf gemeinsam genutzte Daten entstehen. 2 Pthreads - API Im Folgenden soll die Funktionsweise der Funktionen der API von Pthreads erläutert werden. 1 2.1 Thread-Management Zunächst werden die Funktionen zum Erstellen, Beenden und Verwalten von Threads vorgestellt. 2 2.1.1 Thread-Erstellung int p t h r e a d c r e a t e ( p t h r e a d t r e s t r i c t thread, const p t h r e a d a t t r t r e s t r i c t attr, void ( s t a r t r o u t i n e ) ( void ), void r e s t r i c t arg ) ; Diese Funktion erzeugt einen neuen Thread, welcher start routine ausführt. Der erste Parameter thread ist dabei ein Zeiger auf ein Objekt vom Typ pthread t, welches den neuen Thread repräsentiert. Für jeden neu erstellten Thread ist dabei genau ein solcher pthread t-repräsentant notwendig. Mit dem zweiten Parameter attr können spezielle Attribute für den neuen Thread, wie z.b. die Stackgröße gesetzt werden. Wird NULL übergeben, so werden die implementationsabhängigen Standardattribute gesetzt. Als dritten Parameter erwartet pthread create einen Zeiger start routine auf eine Funktion mit Rückgabetyp void und einem Parameter mit Typ void. Der letzte Parameter ist ein Zeiger vom Typ void, welcher an die aufgerufene Funktion übergeben wird. Beispiel: #include <pthread. h> #include <s t d i o. h> #include <s t d l i b. h> void do work ( void param ) { 1 Pthreads-Datentypen werden in blau dargestellt 2 Funktionen für das Thread-Management werden in grün dargestellt 5

int i, a = 0 ; for ( i = 0 ; i <= 100; i++) { a += i ; p r i n t f ( computed %d\n, a ) ; int main ( int argc, char argv [ ] ) { p t h r e a d t thread ; int rc = p t h r e a d c r e a t e (&thread, NULL, do work, NULL) ; i f ( rc ) { p r i n t f ( ERROR; r e turn code from p t h r e a d c r e a t e ( ) i s %d\n, rc ) ; e x i t ( 1) ; p t h r e a d e x i t (NULL) ; 2.1.2 Thread-Ende void p t h r e a d e x i t ( void v a l u e p t r ) ; Die Funktion pthread exit wird benötigt, um einen Rückgabewert von Threads an den Rest des Programms zurückzugeben, der zurückgegebene Wert wird als Zeiger vom Typ void mit dem pthread t-repräsentanten des Threads assoziiert. Der Aufruf von pthread exit ist optional, falls kein Wert zurückgegeben werden soll. Der Thread endet in diesem Fall mit der vollständigen abarbeitung der aufgerufenen Funktion. Lediglich im Falle von main() muss pthread exit aufgerufen werden, falls auf eventuell noch nicht fertig abgearbeitete Threads gewartet werden soll, wie im obigen Beispiel von pthread create. Beispiel: void do work ( void param ) { int i ; int a = 0 ; for ( i = 0 ; i <= 100; i++) { a += i ; p t h r e a d e x i t ( ( void ) a ) ; 6

2.1.3 Thread-Synchronisation: Join int p t h r e a d j o i n ( p t h r e a d t thread, void v a l u e p t r ) ; Mit dem Aufruf von pthread join kann zwischen Threads synchronisiert werden: Der Aufruf blockiert, bis der Thread beendet ist, welcher durch thread repräsentiert wird. In der übergebenen Adresse value ptr wird der Zeiger gespeichert, welche pthread exit im entsprechenden Thread übergeben wurde. Beispiel: p t h r e a d t thread ; int rc = p t h r e a d c r e a t e (&thread, NULL, do work,null) ; i f ( rc ) { p r i n t f ( ERROR; r e turn code from p t h r e a d c r e a t e ( ) i s %d\n, rc ) ; e x i t ( 1) ; void a ; p t h r e a d j o i n ( thread, &a ) ; int i = a ; p r i n t f ( computed %d\n, i ) ; 2.2 Mutex Ein wechselseitiger Ausschluss, häufig auch Mutex (Verkürzung von mutual exclusion) ist ein Verfahren um kritische Abschnitte so zu serialisieren, dass nebenläufige Zugriffe auf gemeinsam genutzte Speicherbereiche keinen inkonsistenten Zustand erzeugen können. Als klassisches Beispiel dient hier das lost-update-problem, allgemeiner spricht man von Race Conditions (Wettlaufsituationen), da das Ergebnis des Programmablaufs von einem,,wettlauf zwischen zwei oder mehr Threads abhängt. Zur Veranschaulichung betrachte man den folgenden Code: #include <pthread. h> #include <s t d i o. h> #include <s t d l i b. h> #define NUM THREADS 10 7

int g l o b a l ; void add50000 ( void param ) { int i ; for ( i = 0 ; i < 50000; i++) { g l o b a l += 1 ; int main ( int argc, char argv [ ] ) { g l o b a l = 0 ; p t h r e a d t thread [NUM THREADS] ; int t, rc ; f o r ( t =0; t<num THREADS; t++){ rc = p t h r e a d c r e a t e (&thread [ t ], NULL, add50000, NULL) ; i f ( rc ) { p r i n t f ( ERROR; r e turn code from p t h r e a d c r e a t e ( ) i s %d\n, rc ) ; e x i t ( 1) ; f o r ( t =0; t<num THREADS; t++) { rc = p t h r e a d j o i n ( thread [ t ], NULL) ; i f ( rc ) { p r i n t f ( ERROR; r e turn code from p t h r e a d j o i n ( ) i s %d\n, rc ) ; e x i t ( 1) ; p r i n t f ( g l o b a l i s %d\n, g l o b a l ) ; Da die Threads unabhängig voneinander die Variable global auslesen, um eins erhöhen, und wieder zurückschreiben, kann hier der Fall auftreten, dass mehrere Threads denselben Wert auslesen, und somit Inkrementierungen verloren gehen. Um dieses Problem zu umgehen ist ein wechselseitiger Ausschluss erforderlich. 3 3 Die zum wechselseitigen Ausschluss gehörenden Funktionen und Makros werden in rot dargestellt 8

2.2.1 Mutex-Initialisierung pthread mutex t mutex = PTHREAD MUTEX INITIALIZER; Zur Initialisierung mit den Standardeinstellungen eines Mutex stellt Pthreads das Makro PTHREAD MUTEX INITIALIZER bereit. Dieses ist gegenüber einem Aufruf von pthread mutex init zu bevorzugen, da für die Standard- Parameter eine statische Initialisierung möglich ist. int p t h r e a d m u t e x i n i t ( pthread mutex t r e s t r i c t mutex, const pthread mutexattr t r e s t r i c t a t t r ) ; Falls erforderlich kann mit pthread mutex init ein Mutex initialisiert werden, der über mehrere Prozesse geteilt wird, oder dessen Prioritätsverwaltung nicht der Standardeinstellung entsprechen soll. Es sollte beachtet werden, dass nicht alle Pthreads-Implementierungen alle Attribute zur Verfügung stellen. 2.2.2 Mutex-Verwendung int pthread mutex lock ( pthread mutex t mutex ) ; int pthread mutex unlock ( pthread mutex t mutex ) ; Diese beiden Funktionen dienen dem Sperren, bzw. Freigeben eines Mutex. Ein Thread, der in eine kritische Sektion gelangt kann mit pthread mutex lock sicherstellen, dass kein anderer Thread dieselbe Sperre hält, der Thread hat somit exklusiven Zugriff auf die vom Mutex geschützten Resourcen. Um den Zugriff auf den Mutex, und damit die Resourcen, wieder freizugeben muss pthread mutex unlock aufgerufen werden. Pthreads selbst stellt keine Konstrukte zur Vefügung, die die Verwendung einer Resource an einen Mutex koppeln, es liegt somit in der Verantwortung des Programmierers sicherzustellen, dass gemeinsam genutzte Resourcen durch wechselseitigen Ausschluss geschützt werden. Folgender Code behebt das zu Beginn gezeigte Problem beim Zugriff auf eine globale Variable: pthread mutex t int g l o b a l ; mutex = PTHREAD MUTEX INITIALIZER; 9

void add50000 ( void param ) { int i ; pthread mutex lock(&mutex ) ; for ( i = 0 ; i < 50000; i++) { g l o b a l += 1 ; pthread mutex unlock(&mutex ) ; 2.3 Conditions Häufig ist es nicht ausreichend einen wechselseitigen Ausschluss für Resourcen zu definieren, da eine bestimmte Bedingung erfüllt sein muss, damit ein Thread seine Arbeit fortsetzen kann. Für die regelmäßige Überprüfung dieser Bedingung wäre es jeweils notwendig alle anderen Threads anzuhalten, die mit der Resource arbeiten, was die Laufzeit des Programms erheblich verlängern kann. Um den wartenden Thread zielgerichtet zu informieren, dass er seine Arbeit fortsetzen kann, stellt Pthreads Conditions (Bedingungen) bereit. 4 2.3.1 Condition-Initialisierung pthread cond t cond = PTHREAD COND INITIALIZER ; int p t h r e a d c o n d i n i t ( pthread cond t r e s t r i c t cond, const p t h r e a d c o n d a t t r t r e s t r i c t a t t r ) ; Ähnlich wie beim wechselseitigen Ausschluss kann eine Condition mit Hilfe des Makros PTHREAD COND INITIALIZER initialisiert werden. Die Funktion pthread cond init ist normalerweise nicht notwendig, da nur eine einzige Eigenschaft zusätzlich gesetzt werden kann: Die Möglichkeit die Condition über mehrere Prozesse zu teilen. Diese Funktionalität wird nicht von allen Pthreads-Implementierungen unterstützt, und sollte nur verwendet werden, wenn dies unbedingt notwendig ist. 2.3.2 Condition-Verwendung int pthread cond wait ( pthread cond t r e s t r i c t cond, pthread mutex t r e s t r i c t mutex ) ; 4 Die zu Conditions gehörenden Funktionen und Makros werden in orange dargestellt 10

Mit dem Aufruf von pthread cond wait wird der aufrufende Thread schlafen gelegt bis ein Signal für diese Condition ausgelöst wird. Dabei wird der Mutex mutex freigegeben, sodass andere Threads auf die von ihm geschützten Resourcen zugreifen können. Sobald die Condition ein Signal erhält, versucht der Thread den Mutex erneut zu sperren, sobald dies gelungen ist wird der Thread fortgesetzt. Dies bedeutet insbesondere, dass vor und nach dem Aufruf von pthread cond wait der übergebene Mutex gesperrt ist, ein Aufruf mit ungesperrtem Mutex ist ein Programmierfehler mit nicht-definiertem Verhalten. int p t h r e a d c o n d s i g n a l ( pthread cond t cond ) ; int pthread cond broadcast ( pthread cond t cond ) ; Die beiden Funktionen pthread cond signal und pthread cond broadcast lösen Signale auf der übergebenen Condition cond aus. Dabei wird pthread cond signal verwendet um einen wartenden Thread zu wecken, pthread cond broadcast hingegen, wenn mehrere Threads auf die Condition warten. Falls mehrere Threads auf die Condition warten und pthread cond signal verwendet wird, so ist lediglich sichergestellt, dass mindestens einer der Threads aufgeweckt wird, der Pthreads-Standard erlaubt hier aber ausdrücklich sogenannete spurious wakeups, d.h. es können mehrere Threads aufgeweckt werden, weshalb diese nach dem Aufwachen und Sperren des Mutex dennoch das mit der Condition signalisierte Prädikat erneut prüfen müssen. Der folgende Programmcode demonstriert die Verwendung von Conditions: #include <pthread. h> #include <s t d i o. h> #include <s t d l i b. h> pthread mutex t mutex = PTHREAD MUTEX INITIALIZER; pthread cond t cond = PTHREAD COND INITIALIZER ; int g l o b a l ; void add50000 ( void param ) { int i, h ; for ( i = 0 ; i < 1 0 ; i ++) { pthread mutex lock(&mutex ) ; for ( h = 0 ; h < 5000; h++) { g l o b a l += 1 ; 11

p t h r e a d c o n d s i g n a l (&cond ) ; pthread mutex unlock(&mutex ) ; s l e e p ( 1 ) ; void p r i n t g l o b a l ( void param ) { while ( g l o b a l < 50000) { pthread mutex lock(&mutex ) ; pthread cond wait (&cond, &mutex ) ; p r i n t f ( g l o b a l=%d\n, g l o b a l ) ; pthread mutex unlock(&mutex ) ; int main ( int argc, char argv [ ] ) { g l o b a l = 0 ; p t h r e a d t thread [ 2 ] ; int t, rc ; rc = p t h r e a d c r e a t e (&thread [ 0 ], NULL, add50000, NULL) ; i f ( rc ) { p r i n t f ( ERROR; r e turn code from p t h r e a d c r e a t e ( ) i s %d\n, rc ) ; e x i t ( 1) ; rc= p t h r e a d c r e a t e (&thread [ 1 ], NULL, p r i n t g l o b a l, NULL) ; i f ( rc ) { p r i n t f ( ERROR; r e turn code from p t h r e a d c r e a t e ( ) i s %d\n, rc ) ; e x i t ( 1) ; for ( t =0; t <2; t++) { rc = p t h r e a d j o i n ( thread [ t ], NULL) ; i f ( rc ) { p r i n t f ( ERROR; r e turn code from p t h r e a d j o i n ( ) i s %d\n, rc ) ; e x i t ( 1) ; 12

2.4 Thread-Abbruch Zum Beispiel bei spekulativer Ausführung eines Threads kann es sinnvoll sein diesen vorzeitig abzubrechen. Jedoch muss dabei darauf geachtet werden, dass kein inkonsistenter Programmzustand entsteht. 5 int p t h r e a d c a n c e l ( p t h r ead t thread ) ; Um einen Thread abzubrechen muss sein Repräsentant an pthread cancel übergeben werden. 2.4.1 Abbruch-Arten int p t h r e a d s e t c a n c e l s t a t e ( int s t a t e, int o l d s t a t e ) ; int p t h r e a d s e t c a n c e l t y p e ( int type, int oldtype ) ; Neu erstellte Threads können standardmäßig abgebrochen werden. Um zu verhindern, dass ein Thread abgebrochen werden kann setzt man mit pthread setcancelstate den Zustand auf PTHREAD CANCEL DISABLE, um den Ursprungszustand wiederherzustellen auf PTHREAD CANCEL ENABLE. Pthreads kennt zwei unterschiedliche Arten mit Thread-Abbruch umzugehen, was mit pthread setcanceltype eingestellt werden kann. Initial werden Threads verzögert abgebrochen. Konkret heißt dies, dass ein Thread bis zum nächsten cancellation point (Abbruchspunkt) weiterläuft, und erst dort abgebrochen wird. Eine Liste der Abbruchspunkte findet sich in der man-page zu pthreads [man08]. Alternativ können Threads sofort abgebrochen werden, dies bedeutet jedoch einen erhöhten Aufwand bei der Sicherstellung eines konsistenten Zustandes. Um einen Thread ohne Verzögerung abbrechen zu können setzt man mit pthread setcanceltype seinen Typ auf PTHREAD CANCEL ASYNCHRONOUS, für verzögertes Abbrechen stellt man ihn auf PTHREAD CANCEL DEFERRED. 2.4.2 Aufräumen void pthread cleanup push ( void ( r o u t i n e ) ( void ), void arg ) ; void pthread cleanup pop ( int execute ) ; Um im Falle eines Thread-Abbruchs notwendige Aufräumarbeiten durchzuführen müssen entsprechende Aufräum-Routinen mit pthread cleanup push auf den Aufräum-Stack geschrieben werden. 5 Funktionen und Makros des Thread-Abbruchs werden wie Thread-Management- Funktionen in grün dargestellt 13

Beim Abbruch wird dann arg als Parameter an routine übergeben. Wird der Thread nicht abgebrochen, so kann mit pthread cleanup pop die neueste Aufräum-Routine entfernt werden. Falls execute dabei ungleich 0 ist, so wird die Routine ausgeführt. 3 Probleme 3.1 Implementierungsvielfalt Durch die enorme Menge von Implementierungen ist es schwierig portable Programme zu schreiben, die auf allen Implementierungen gut laufen. Um sicher zu gehen muss auf viele Features verzichtet werden, und die Stack- Größe sollte manuell auf ausreichende Werte für jeden Thread gesetzt werden [Bar09]. Pthreads bietet zwar Möglichkeiten Threads mit unterschiedlichen Prioritäten und Scheduling-Verfahren ablaufen zu lassen, doch die Implementierungen müssen dies nicht zwingend unterstützen. Zum Beispiel unterstützt NPTL Thread-Prioritäten nicht in den Standardeinstellungen. Setzt man jedoch einen anderen Scheduler ein, so entstehen Echtzeit-Threads, welche mit Prioritäten 0-99 versehen werden können. Es sind root-rechte für den Wechsel zu Echtzeit-Threads erforderlich. Für weitere Details zu Prioritäten und damit verbundenen Implementierungs- Unterschieden siehe [Mai06] Generell sind zudem die durch die init-funktionen bei Mutex und Condition setzbaren Attribute nicht in jeder Implementierung vorhanden, was ihre Verwendbarkeit für portable Programme stark einschränkt. Zu diesen allgemeinen Interoperabilitätsproblemen kommen subtile Unterschiede durch die eingesetzten Scheduling-Modelle. So können offensichtlich bei GNU portable Threads keine echten Wettlaufsituationen auftreten, da diese Implementierung keine Hardware-Parallelität nutzt. Aber auch die anderen beiden Modelle haben kleinere Unterschiede, weswegen FreeBSD in Version 7 sowohl das N:M-Modell zur Abwärtskompatibilität anbietet, als auch eine neue Thread-Implementierung mit 1:1-Modell unterstützt. 14

3.2 Prioritätsinversion Prioritätsinversion bezeichnet das Problem, dass Threads mit niedriger Priorität abgearbeitet werden, obwohl ein Thread mit höherer Priorität wartet. Dieser Effekt kann bei Pthreads auftreten, wenn ein Thread mit niedriger Priorität einen Mutex sperrt, auf den anschließend ein Thread hoher Priorität wartet. Werden nun gleichzeitig viele Threads mittlerer Priorität ausgeführt, so bekommt der niedrig priorisierte Thread keine Rechenzeit mehr zugeteilt, und kann somit den Mutex auch nicht mehr freigeben. Der Thread mit hoher Priorität muss also auf das Ende der Threads mittlerer Priorität warten. Um dieses Problem zu umgehen bietet Pthreads die Möglichkeit Threads vorübergehend eine höhere Priorität zuzuweisen, während sie einen Mutex gesperrt halten. Dies verhindert das Auftreten von Prioritätsinversion wie oben beschrieben, solange kein Thread mit höherer Priorität als der vorübergehend gewährten auf den Mutex wartet. Leider gilt auch hier, dass diese Funktionalität nicht von allen Implementierungen zur Verfügung gestellt wird. 4 Alternativen zu Pthreads Solaris Threads Solaris Threads ähneln Pthreads vom Aufbau her stark, die Funktionen haben allerdings andere Namen [Sun08]. Einige wenige Funktionen stehen in Pthreads nicht zur Verfügung, allerdings können Solaris Threads und Pthreads kooperativ im selben Programm verwendet werden, wodurch die Vorteile beider Schnittstellen genutzt werden können. Solaris Threads laufen, wie der Name bereits andeutet, nur auf Solaris-Betriebssystemen. Boost.Thread Für das Programmieren in C++ bietet es sich an eine abstraktere Thread- Bibliothek als Pthreads zu verwenden. Boost.Thread ist eine frei verfügbare Thread-Bibliothek für C++, die je nach Betriebssystem eine passende Thread-Implementierung verwendet. Unter Linux und weiteren Betriebssystemen wird Pthreads als Grundlage verwendet, es werden aber nicht alle Pthreads-Funktionen zur Verfügung gestellt. So ist das Abbrechen von Threads in C++ prinzipiell nicht zu empfehlen, da es den Aufruf von Destruktoren unterdrücken kann, und somit Speicherlecks auslösen kann. 15

Für den Programmierer bieter Boost.Thread gegenüber Pthreads einfachere Strukturen, wie zum Beispiel scoped lock, welches einen Mutex bei seiner Erstellung sperrt, und beim Verlassen des Sichtbarkeitsbereiches wieder freigibt. Außerdem können Threads zu Gruppen zusammengefasst werden, was die Verwaltung vereinfachen kann. OpenMP Bei vielen bestehenden Programmen kann eine Geschwindigkeitssteigerung durch Parallelisierung von Schleifen erreicht werden. Hierfür ist OpenMP besser geeignet als Pthreads, da es dem Programmierer die Arbeit des Thread- Erstellens abnimmt, und erweiterte Funktionen für parallele Schleifen bietet. Die automatische Erstellung von Threads bietet den Vorteil auch bei vielen Kernen eine Geschwindigkeitssteigerung zu erziehlen, ohne wie in Pthreads selbst mehr Ausführungsstränge anlegen zu müssen. Allerdings ist OpenMP weniger flexibel als Pthreads. Es eignet sich nicht um Threads zu erzeugen, welche unterschiedlichen Aufgaben innerhalb des Programms übernehmen. MPI Da Pthreads einen gemeinsam genutzten Speicher voraussetzt ist es nicht beliebig skalierbar. Auf NUMA-Systemen muss der Programmierer beim Verwenden von Pthreads beachten, dass der Speicher desjenigen Prozessors belegt wird, auf dem der speicherbelegende Thread gerade läuft. Dies führt häufig zu Engpässen bei der Datenübertragung zwischen den Prozessoren. Um auch verteilten Speicher nutzen zu können ist im Moment MPI das Mittel der Wahl. Es erlaubt nicht nur die effiziente Nutzung des gemeinsamen Speichers auf NUMA-Systemen, sondern kann auch für Cluster-Systeme eingesetzt werden. Diese höhere Flexibilität und Skalierbarkeit sollte allerdings gegen die erheblich schwierigere Benutzung abgewogen werden. 16

Literatur [Bar09] Blaise Barney. POSIX Threads Programming. Website, 2009. Available online at https://computing.llnl.gov/tutorials/pthreads/. [Eng06] Ralf S. Engelschall. GNU Pth - The GNU Portable Threads. Website, 2006. Available online at http://www.gnu.org/software/pth/. [Joh09] Ross Johnson. Open Source POSIX Threads for Win32. Website, 2009. Available online at http://sourceware.org/pthreads-win32/. [Mai06] Gregor Maier. Thread Scheduling with pthreads under Linux and FreeBSD. Website, 2006. Available online at http://www.net.t-labs.tu-berlin.de/~gregor/tools/ pthread-scheduling.html. [man08] Linux man-pages project. pthreads - POSIX threads, 2008. Available online at http://www.kernel.org/doc/man-pages/online/pages/man7/ pthreads.7.html. [Qui04] Michael J. Quinn. Parallel Programming in C with MPI and OpenMP, pages 405 406. McGraw-Hill, 2004. [Sun08] Multithreaded Programming Guide. Website, 2008. Pages 174 177, content available online at http://docs.sun.com/app/docs/doc/816-5137/ sthreads-96692?a=view. 17