Shared-Memory Programmiermodelle mehrere, unabhängige Programmsegmente greifen direkt auf gemeinsame Variablen ( shared variables ) zu Prozeßmodell gemäß fork/join Prinzip, z.b. in Unix: fork: Erzeugung eines neuen Kind-Prozesses (komplette Kopie des Elternprozesses zum Zeitpunkt der Erzeugung) wait: Elternprozess kann auf Terminierung der Kindprozesse warten Kommunikation der Prozesse über Signale, Sockets und ggf. auch über ein gemeinsames Speichersegment, das durch Systemfunktionen wie z.b. shmget, shmat, shmdet und shmop kontrolliert wird Threadmodell: mehrere unabhängige Kontrollflüsse in einem Programm bilden jeweils einen Thread Threads teilen sich einen globalen Adressraum Implementierung von Threads durch Bibliotheken, z.b. Pthreads (POSIX-Threads) für Unix 28 Shared-Memory Programmiermodelle (Forts.) Möglichkeiten der Implementierung eines Scheduling für Threads: 1) auf Anwender-Ebene: alle Anwender-Threads werden durch eine Bibliothek in einem Prozess realisiert; Vorteil: sehr schneller Thread- Wechsel ohne Beteiligung des Betriebssystems; Nachteil: kein Thread- Wechsel möglich, wenn ein Thread durch E/A-Operation blockiert ist 2) auf Betriebssystem-Ebene: jeder Anwender-Thread entspricht einem Kernel-Thread ( Leight-Weight Process, LWP); Vorteil: Zuordnung mehrerer Prozessoren zu Threads; Nachteil: nicht jedes Betriebssystem unterstützt Kernel-Threads! Beispiel: Linux Threads, Windows NT 3) auf Anwender- und Betriebssystem-Ebene: der Scheduler der Thread- Bibliothek bildet Anwender-Threads auf Betriebssystem-Threads ab; Scheduler des Betriebssystems bildet letztere ggf. auf Prozessoren ab Beispiel: Solaris 2 29
Thread-Programmierung Möglichkeiten der Arbeitsverteilung auf Threads: Master/Slave: ein Master-Thread steuert Programm, erzeugt mehrere Slave-Threads und verteilt Arbeit gleichmäßig auf Threads Pipelining: Thread i produziert Daten für Thread i +1 Pool: mehrere Threads holen sich nach Abarbeitung einer Aufgabe eine neue Aufgabe aus einem Aufgaben-Pool Divide-and-Conquer: jeder Thread erzeugt rekursiv einen weiteren Thread, bis Aufgabe ausreichend fein zerlegt ist Wettbewerb: jeder Thread führt eine andere Strategie aus jeder Thread hat Zugriff auf alle globalen Variablen und dynamisch erzeugte Objekte im globalen Adressraum, besitzt jedoch lokalen Laufzeitstack für private Variablen explizite Synchronisation beim Zugriff auf globale Variablen über Semaphore (Mutex-Variablen) 30 Posix Threads (Pthreads) ANSI/IEEE Standard (1995) zur betriebssystem-unabhängigen Thread-Programmierung das Thread-Scheduling ist jedoch betriebssystem-abhängig! Generierung eines Threads: int pthread_create (pthread_t *thread-id, const pthread_attr_t *attr, void *(*start_func) (void*), void *arg) mit thread-id : eindeutige Identifikation des erzeugten Threads attr : Datenstruktur, mit der einige Attribute des zu erzeugenden Threads festgelegt werden können, ansonsten NULL start_func : Name einer Startfunktion, die Thread nach Erzeugung ausführen soll arg : ein Parameter für Funktion start_func 31
Pthreads (Forts.) maximale Anzahl erzeugbarer Threads ist systemabhängig, jedoch mindestens 64 (gemäß POSIX-Standard) blockierendes Warten auf Beendigung eines Threads: int pthread_join (pthread_t thread-id, void **return_value) ein Thread kann seinen eigenen Bezeichner erfahren: pthread_t pthread_self(void) ein Thread terminiert bei Ende der in pthread_create angegebenen Startfunktion vorzeitig durch expliziten internen Aufruf der Funktion void pthread_exit (void *return_value) wenn er durch anderen Thread explizit abgebrochen wird: int pthread_cancel(pthread_t thread) wenn erzeugender Eltern-Thread terminiert 32 Pthreads Beispiel 1: Hello World #include <stdio.h> #define NUM_THREADS 5 void *PrintHello( void *id ) { printf( Hello World from Thread %d\n, id); pthread_exit( (void*) 0 ); main( int argc, char *argv[] ) { int status, t; pthread_t tids[num_threads]; for (t=0; t< NUM_THREADS, t++) { printf( Creating thread %d\n, t); if (pthread_create(&tids[t], NULL, PrintHello, (void*) t)!= 0){ printf( Error in pthread_create!\n"); exit(1); for (t=0; t< NUM_THREADS, t++) { if (pthread_join(tids[t], (void**)&status)!= 0) { printf( Error in pthread_join!\n"); exit(1); if (status == 0) printf( Thread %d terminated correctly.\n, t); 33
Pthreads Beispiel 2: Matrix-Multiplikation double X[DIM][DIM], M[DIM][DIM], Y[DIM][DIM]; typedef struct {int row; int col; work_t; void* matmult(void* arg) { int i; work_t *work = (work_t*) arg; Y[work->row][work->col]=0; for (i=0; i < DIM; i++) Y[work->row][work->col] += X[work->row][i] * M[i][work->col]; int main( int argc, char *argv[] ) { int i,j; work_t work[dim][dim]; pthread_t tid[dim][dim]; for (i=0; i < DIM; i++) for (j=0; j < DIM; j++) { work[i][j].row = i; work[i][j].col = j; pthread_create(&tid[i][j], NULL, matmult,(void*) &work[i][j]); for (i=0; i < DIM; i++) for (j=0; j < DIM; j++) pthread_join(tid[i][j], NULL); 34 Pthreads (Forts.) Koordination von Threads durch Mutex-Variablen: Deklaration einer Mutex-Variablen für einen gegenseitigen Ausschluß pthread_mutex_t *mutex Initialisierung einer Mutex-Variablen (Default mit attr=null) int pthread_mutex_init (pthread_mutex_t *mutex, const pthread_mutexattr_t *attr) Thread, der zuerst eine gemeinsame Datenstruktur modifizieren möchte, sperrt zuvor die zugehörige Mutex-Variable int pthread_mutex_lock (pthread_mutex_t *mutex) ruft ein anderer Thread diese Funktion mit der gleichen Mutex-Variablen auf, so wird er blockiert, bis Mutex-Variable wieder frei ist nach Beendigung des Zugriffs gibt Thread die zugehörige Mutex- Variable wieder frei: int pthread_mutex_unlock (pthread_mutex_t *mutex) Test, ob Mutex-Variable bereits durch einen anderen Thread belegt ist: int pthread_mutex_trylock (pthread_mutex_t *mutex) 35
Pthreads Beispiel 3: Skalarprodukt #define NUMTHRDS 4 #define VECLEN 100000 double a[veclen]; double b[veclen]; double sum; pthread_mutex_t mutexsum; /* already initialized in main */ void *dotprod(void *tid) { int i, len, start, end; double mysum = 0; len = VECLEN / NUMTHRDS; start = (int) tid * len; end = start + len; for (i=start; i<end ; i++) /* compute local part of dot product */ mysum += (a[i] * b[i]); pthread_mutex_lock (&mutexsum); sum += mysum; /* critical region */ pthread_mutex_unlock (&mutexsum); pthread_exit((void*) 0); 36 Pthreads (Forts.) Koordination von Threads durch Bedingungsvariablen: Deklaration einer Bedingungsvariablen: pthread_cond_t *cond Initialisierung einer Bedingungs-Variablen (Default mit attr=null): int pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr) Thread wird blockiert, bis die zu cond gehörende Bedingung eintritt: int pthread_cond_wait (pthread_cont_t *cond, pthread_mutex_t *mutex) jede Bedingungsvariable ist mit einer Mutex-Variable assoziiert: vor pthread_cond_wait muss daher pthread_mutex_lock(mutex) aufgerufen werden; während einer Blockierung ist mutex jedoch frei! Sobald Bedingung in einem anderen Thread erfüllt ist, kann dieser einen oder alle bzgl. der Variablen cond blockierten Threads aufwecken: int pthread_cond_signal (pthread_cond_t *cond) int pthread_cond_broadcast (pthread_cond_t *cond) 37
Pthreads Beispiel 4: Thread-Pool #define NUM_THREADS 10 #define MAX_TASKS 100 typedef struct { pthread_mutex_t lock; pthread_cond_t empty, not_full; /*... further variables for task queue ( not shown here )... */ work_t; void main() { int i; work_t *work; pthread_t tids[num_threads]; pthread_mutex_init(work->lock); pthread_cond_init(work->empty); pthread_cond_init(work->not_full); for (i=0; i < NUM_THREADS; i++) { pthread_create(&tids[i], NULL, (void*) worker, (void*) work); 38 Pthreads Beispiel 4: Thread-Pool (Forts.) void worker(work_t *work) { while (TRUE) { pthread_mutex_lock(work->lock); while (work->num_of_tasks == 0) pthread_cond_wait(work->not_empty, work->lock); /*... get next job from task queue ( not shown here )... */ if (work->num_of_tasks == MAX_TASKS) pthread_cond_broadcast(work->not_full); work->num_of_tasks--; pthread_mutex_unlock(work->lock); /*... start next job from task queue ( not shown here )... */ void insert_job (work_t *work) { pthread_mutex_lock(work->lock); while (work->num_of_tasks == MAX_TASKS) pthread_cond_wait(work->not_full, work->lock); if (work->num_of_tasks == 0) pthread_cond_signal(work->not_empty); /*... put job in task queue ( not shown here )... */ work->num_of_tasks++; pthread_mutex_unlock(work->lock); 39