Preview only show first 10 pages with watermark. For full document please download

Synchronisation

   EMBED


Share

Transcript

Gliederung 1. Einführung und Übersicht 2. Prozesse und Threads 3. Interrupts 4. Scheduling Synchronisation 5. Synchronisation 6. Interprozesskommunikation 7. Speicherverwaltung BS-I / Cl. Schnörr / HM Synchronisation 1 BS-I / Cl. Schnörr / HM Gliederung Synchronisation 2 Einführung (1) Wozu Synchronisation ? Übersicht:  Einführung  Threads und z.T. Prozesse haben gemeinsamen Zugriff auf bestimmte Daten, z.B.  Threads des gleichen Prozesses:  Race-Condition  gemeinsamer virtueller Speicher  Kritische Abschnitte  öffnen der gleichen Datei zum Lesen/Schreiben  Mechanismen des BS und Standard-Primitive  Prozesse mit Shared-Memory (--> IPC)  Tips zur praktischen Anwendung:  SMP-System: Scheduler (je einer pro CPU):  Deadlocks vermeiden  Mutex-Objekt  Zugriff auf gleiche Prozesslisten / Warteschlangen  Datenbanken:  Zugriff über eine globale Datenbank-Verbindung (DB-Connect) BS-I / Synchronisation Cl. Schnörr / HM 3 BS-I / Synchronisation Cl. Schnörr / HM 4 Einführung (2) Einführung (3) Beispiel: gleichzeitiger Zugriff auf Datenstruktur: Beispiel: gleichzeitiger Zugriff auf Datenstruktur:  zwei Threads erhöhen einen gemeinsamen Zähler:  gewünscht: eine der folgenden koordinierten Reihenfolgen: BS-I / Cl. Schnörr / HM Synchronisation 5 BS-I / Synchronisation Einführung (4) Beispiel: Datenbanken:  Ursache:  Datenbank: analoges Problem: Cl. Schnörr / HM 8 exec sql CONNECT ... exec sql SELECT kontostand INTO $var FROM KONTO WHERE kontonummer = $knr $var = $var - abhebung exec sql UPDATE Konto SET kontostand = $var WHERE kontonummer = $knr exec sql disconnect  erhoehe_zaehler() arbeitet nicht atomar:  Scheduler kann Funktion unterbrechen (anderer Thread arbeitet weiter)  Funktion kann auf mehreren CPUs gleichzeitig laufen  Lösung: stelle sicher, dass  paralleler Zugriff auf gleichen Datensatz potentiell fehlerhaft  immer nur ein Prozess/Thread gleichzeitig auf gemeinsame Daten zugreift  --> Definition der (Datenbank-) Transaktion, die  bis Vorgang abgeschlossen ist  u.a. atomar und isoliert erfolgen muss  == gegenseitiger Ausschluß (Mutual Exclusion) Synchronisation 6 Einführung (5) Beispiel: gleichzeitiger Zugriff auf Datenstruktur: BS-I / Cl. Schnörr / HM Cl. Schnörr / HM 7 BS-I / Synchronisation Race-Condition (1) Race-Condition (2) Eigenschaften: Race-Condition als Sicherheitslücke  Race Condition  Race-Conditions sind auch Sicherheitslücken  mehrere parallele Threads/Prozesse nutzen gemeinsame Ressource  wird vom Angreifer genutzt  Zustand hängt von Reihenfolge der Ausführung ab:  einfaches Beispiel:  Ergebnis nicht vorhersagbar / nicht reproduzierbar read( command ); f = open( “/tmp/script“, “w“ ); write( f, command ); close( f ); chmod( “/tmp/script“, “a+x“ ); system( “/tmp/script“ );  Race: Threads liefern sich ein „Rennen“ um den ersten/schnellsten Zugriff Warum Race-Conditions vermeiden?  Ergebnisse paralleler Berechnungen sind potentiell falsch  Angreifer ändert Dateiinhalt vor dem chmod()  Programmtests können „funktionieren“, später die Anwendung aber versagen BS-I / Cl. Schnörr / HM Synchronisation / Race-Condition --> Programm läuft mit Rechten des Opfers 9 BS-I / Synchronisation / Race-Condition Race-Condition (3) Zugriff via Flag / Lock auf Thread/Prozess beschränken: erhoehe_zaehler() { flag = read( lock ); if ( flag == LOCK_NOT_SET ) { set( lock ); //Start kritischer Bereich w = read( adr ); w = w + 1; write( adr, w ); //Ende kritischer Bereich release( lock ); } }  Problem: Lock-Variable nicht geschützt BS-I / Synchronisation / Race-Condition 10 Kritischer Bereich Was ist ein „kritischer Bereich“ ? Aspekte einer Lösung  Idee: Cl. Schnörr / HM  kein Problem:  Programmteil, der auf gemeinsame Daten zugreift  gleichzeitiges Lesen von Daten  Block zwischen erstem und letztem Zugriff  Threads, die “disjunkt“ sind, d.h.  Formulierung: kritischen Bereich  betreten / verlassen (enter / leave critical section)  keine gemeinsame Daten haben / nutzen Anforderungen an parallele Threads:  problematisch:  maximal ein Thread gleichzeitig in kritischem Bereich  mehrere Prozesse/Threads  kein Thread außerhalb kritischem Bereich darf anderen blockieren (--> potentiell Deadlock)  greifen gemeinsam auf Objekt zu,  kein Thread soll ewig auf Freigabe eines kritischen Bereichs warten  davon mindestens einer schreibend  Deadlocks zu vermeiden, z.B.  zwei Threads in verschiedenen kritischen Bereichen blockieren sich gegenseitig Cl. Schnörr / HM 11 BS-I / Synchronisation / kritischer Bereich Cl. Schnörr / HM 12 Gegenseitiger Ausschluß Programmtechnische Synchronisation  Gegenseitiger Ausschluß (Mutual Exclusion, kurz Mutex):  Idee war:  nie mehr als ein Thread betritt kritischen Bereich  Zugriff via Flag / Lock auf Thread/Prozess beschränken  es ist Aufgabe des Programmierers, dies zu garantieren  Problem:  das BS bietet Mechanismen und Hilfsmittel, gegenseitigen Ausschluß durchzusetzen  Lock-Variable nicht geschützt  Verantwortung für Fehlerfreiheit liegt beim Programmierer:  Lösung:  Compiler können i.d.R. Fehler aus Nebenläufigkeit nicht erkennen  eine Lock-Variable atomar testen und setzen  dies kann per Programmlogik über mehrere Variable erreicht werden (--> Lit.: Dekker1966, Petersons Algorithmus)  ist kompliziert bei mehr als zwei Threads/Prozessen  besser: Nutzung von „standard“ BS-Mechanismen BS-I / Cl. Schnörr / HM Synchronisation / Mutex 13 BS-I / Cl. Schnörr / HM Synchronisation 14 Test-and-Set-Lock (TSL)  Maschineninstruktion (moderner CPUs)  mit dem Namen TSL = Test and Set Lock  die atomar eine Lock-Variable liest (testet) und setzt, also garantiert ohne Unterbrechung  im Fall mehrerer CPUs: Synchronisation  TSL muss Speicherbus sperren, damit kein Thread auf anderer CPU in gleicher Weise zugreifen kann Mechanismen und Standard-Primitive enter: tsl register, flag cmp register, 0 jnz enter ret leave: mov flag, 0 ret BS-I / Synchronisation / Mechanismen Cl. Schnörr / HM 15 BS-I / ; ; ; ; Variable in Register kopieren und dann Variable auf 1 setzen war Variable 0 ? nicht 0: Lock war gesetzt, also Schleife(pollen) ; 0 in flag speichern: Lock freigeben Synchronisation / Mechanismen+Primitive Cl. Schnörr / HM 16 Aktives und passives Warten (1) Aktives und passives Warten (2)  Aktives Warten (busy waiting):  Passives Warten (sleep and wake):  Ausführen einer Schleife, bis eine Variable einen bestimmten Wert annimmt  Thread blockiert und wartet auf Ereignis, das ihn in den Zustand „bereit“ versetzt  Thread ist bereit und belegt die CPU  blockierter Thread verschwendet keine CPU-Zeit  Variable muss von einem anderen Thread gesetzt werden  anderer Thread muss Eintreten des Ereignisses bewirken  (kleines) Problem, wenn anderer Thread endet  (großes) Problem, wenn der andere Thread endet  bei Ereignis muss blockierter Thread geweckt werden  (großes) Problem, wenn anderer Thread -- z.B. wegen niedriger Priorität -- nicht dazu kommt, Variable zu setzen  explizit durch anderen Thread  durch Mechanismen des BS  auch Pollen/Polling genannt BS-I / Synchronisation / Mechanismen+Primitive Cl. Schnörr / HM 17 BS-I / Synchronisation / Mechanismen+Primitive Erzeuger-Verbraucher-Problem (1) Cl. Schnörr / HM 18 Erzeuger-Verbraucher-Problem (2)  Synchronisation:  Erzeuger-Verbraucher-Problem (producer-consumer problem, bounded-buffer problem)  Puffer nicht überfüllen:  zwei kooperierende Threads:  wenn Puffer voll, muß Erzeuger warten, bis Verbraucher ein weiteres Paket abgeholt hat  Erzeuger speichert Informationspaket in beschränktem Puffer  nicht aus leerem Puffer lesen:  Verbraucher liest Informationen aus diesem Puffer  wenn Puffer leer, muß Verbraucher warten, bis Erzeuger ein weiteres Paket abgelegt hat  Realisierung mit passivem Warten:  eine gemeinsame Variable „count“ zählt belegte Positionen im Puffer  wenn Erzeuger ein Paket einstellt und Puffer leer war (count == 0) --> wecken des Verbrauchers  wenn Verbraucher ein Paket abholt und Puffer voll war (count == max) --> wecken des Erzeugers BS-I / Synchronisation / Mechanismen+Primitive Cl. Schnörr / HM 19 BS-I / Synchronisation / Mechanismen+Primitive Cl. Schnörr / HM 20 Erzeuger-Verbraucher mit sleep-wake #define N 100 int count = 0; Deadlock-Problem bei sleep / wake (1) //Anzahl der Plätze im Puffer //Anzahl der belegten Plätze im Puffer  Verbraucher liest Variable count, die den Wert 0 hat producer() { while (TRUE) { // Endlosschleife produce( item ); // Erzeuge etwas für den Puffer // Wenn Puffer voll: schlafen legen if (count == N) sleep(); enter( item ); // In den Puffer einstellen count = count + 1; //Zahl belegter Plätze inkrementieren if (count == 1) wake(consumer); //war der Puffer vorher leer? } }  Kontextwechsel zum Erzeuger:  Erzeuger stellt etwas in den Puffer,  erhöht count und  weckt Verbraucher, da count==0 war consumer() { while (TRUE) { // Endlosschleife // Wenn Puffer leer: schlafen legen if (count == 0) sleep(); remove_item (item); // Etwas aus dem Puffer entnehmen count = count - 1; // Zahl belegter Plätze dekrementieren if (count == N-1) wake(producer); //war der Puffer vorher voll? consume_item (item); // Verarbeiten } } BS-I / Synchronisation / Mechanismen+Primitive Cl. Schnörr / HM  Race-Condition im vorigen Programm --> potentieller Deadlock, z.B. 21  Kontextwechsel zum Verbraucher: Verbraucher legt sich schlafen, da count==0 gelesen wurde  Erzeuger schreibt Puffer voll und legt sich auch schlafen BS-I / Synchronisation / Mechanismen+Primitive Deadlock-Problem bei sleep / wake (2) Cl. Schnörr / HM 22 Cl. Schnörr / HM 24 Standardprimitive zur Synchronisation  Ursache des Problems: Welche Standardprimitive zur Synchronisation gibt es ?  Wakeup-Signal für einen -- noch nicht -- schlafenden Prozess wird ignoriert  Mutex (mutual exclusion) = binärer Semaphor  --> Weckaufruf „irgendwie“ aufbewahren  Semaphor  Lösungsmöglichkeit:  Event (ähnlich Condition-Variable)  Systemaufrufe sleep() und wake() verwenden „wakeup pending bit“  Monitor  bei wake() für nicht schlafenden Thread dessen wakeup-pending-bit setzen  Locking  bei sleep() das wakeup-pending-bit des Threads prüfen und falls gesetzt, nicht schlafen legen  aber:  Lösung lässt sich nicht verallgemeinern (mehrere Prozesse benötigen weitere Bits). BS-I / Synchronisation / Mechanismen+Primitive Cl. Schnörr / HM 23 BS-I / Synchronisation / Mechanismen+Primitive Mutex (1) Mutex (2)  wait() und signal()-Operationen sind selbst kritische Abschnitte --> atomar realisiert  Mutex (mutual exclusion) = binärer Semaphor  Implementierung als System-Calls und Verhinderung von Kontext-Wechseln (z.B. durch kurzzeitiges Ausschalten von Interrupts)  zur Synchronisation eines kritischen Bereichs bzw. gemeinsamer Daten  kann nur 2 Zustände / Werte annehmen: Zugang erlaubt  true / frei:  false / gesperrt: Zugang gesperrt wait( mutex ) { if ( mutex == 1 ) mutex = 0; else BLOCK_CALLER; }  Anforderungs- und Freigabe-Operationen:  Anforderung: wait() / lock() / get() signal() / unlock() / release()  Freigabe:  Anforderung: ein Thread, der eine bereits vergebene Mutex anfordert, blockiert --> Warteschlange  Freigabe:  Warteschlange enthält Threads  Warteschlange leer BS-I / --> einen wecken --> Mutex auf true Synchronisation / Mechanismen+Primitive Cl. Schnörr / HM 25 BS-I / Synchronisation / Mechanismen+Primitive Semaphor (1) 26 Cl. Schnörr / HM 28  Varianten:  Integer- (Zähler-) Variable  negative Semaphor-Werte:  mit festgelegtem Anfangswert N („Anzahl verfügbarer Ressourcen“)  Anforderung (wait()): - Wert immer um 1 reduzieren - --> Wert entspricht Zahl blockierter Threads in Warteschlange  Anforderung (wait()): Wert um 1 reduzieren Thread blockieren --> Warteschlange  Freigabe (signal()): - Wert immer um 1 erhöhen  Freigabe (signal()):  falls Warteschlange nicht leer:  falls Warteschlange leer:  nicht-blockierend: z.B. bool ret = try_lock(); einen Thread wecken Wert um 1 erhöhen  pthreads-Semaphore:  sind auch zwischen Prozessen verwendbar (Standard: nur Threads)  SysV-IPC Semaphore:  arbeiten prozessübergreifend  sind nach Prozessende noch vorhanden wait( &sem ); // Kode, der die Ressource nutzt signal( &sem ); BS-I / Cl. Schnörr / HM Semaphor (2)  Semaphor:  falls >= 1:  falls == 0: signal( mutex ) { if ( P in QUEUE(mutex)) { wakeup( P ); remove( P, QUEUE ); } else mutex = 1; } Synchronisation / Mechanismen+Primitive Cl. Schnörr / HM 27 BS-I / Synchronisation / Mechanismen+Primitive Semaphor (3) Erzeuger-Verbraucher-Problem mit Semaphoren Beispiel:  Verbraucher: Threads A,B,C: wait()  Erzeuger: signal() Thread D: typedef int semaphore; semaphore mutex = 1; semaphore empty = N; semaphore full = 0;  Semaphore s zählt verfügbare Ressource BS-I / Cl. Schnörr / HM Synchronisation / Mechanismen+Primitive 29 // Kontrolliert Zugriff auf Puffer // Zählt freie Plätze im Puffer // Zählt belegte Plätze im Puffer producer() { while (TRUE) { produce_item(item); wait(empty); wait(mutex); enter_item(item); signal(mutex); signal(full); } } // // // // // // // Endlosschleife Erzeuge etwas für den Puffer Leere Plätze dekrementieren bzw. blockieren Eintritt in den kritischen Bereich In den Puffer einstellen Kritischen Bereich verlassen Belegte Plätze erhöhen, evtl. consumer wecken consumer() { while (TRUE) { wait(full); wait(mutex); remove_item(item); signal(mutex); signal(empty); consume_entry(item) } } // // // // // // // Endlosschleife Belegte Plätze dekrementieren bzw. blockieren Eintritt in den kritischen Bereich Aus dem Puffer entnehmen Kritischen Bereich verlassen Freie Plätze erhöhen, evtl producer wecken Verbrauchen BS-I / Cl. Schnörr / HM Synchronisation / Mechanismen+Primitive Events (1) 30 Events (2)  Events: Beispiel:  kein Thread besitzt einen Event (<-> Mutex)  wait() blockiert, falls Event nicht im signalisierten Zustand  automatischer Event: thread A . event.wait() . . thread B . . event.signal(); . manual automatic t # threads waiting after each call:  jedes wait() setzt Event automatisch zurück (reset) 2 2 0 1 0 0 0 0 0 0 0 1 0 2 0 0 manual automatic signalled  falls mehrere Threads warten: bei einem signal() kehrt nur genau ein Thread von seinem wait() zurück non-signalled Zeit t  manueller Event: signal() signal() signal() signal() wait() wait() reset()  Event muß manuell zurückgesetzt werden (reset)  falls mehrere Threads warten: bei einem signal() kehren alle Threads von ihren wait() zurück BS-I / Synchronisation / Mechanismen+Primitive Cl. Schnörr / HM 31 BS-I / Synchronisation / Mechanismen+Primitive Cl. Schnörr / HM 32 Monitor (1) Monitor (2)  Problem bei Arbeit mit Mutexen und Semaphoren:  Programmierer gezwungen, kritische Bereiche mit wait() und signal() abzusichern  schon bei einem Fehler --> Synchronisierung versagt  Monitor:  muss von Programmiersprache unterstützt werden (z.B. Java, Concurrent-Pascal)  Sammlung von Prozeduren, Variablen, speziellen Bedingungsvariablen:  Prozesse können Prozeduren des Monitors aufrufen, aber  nicht auf dessen Datenstrukturen zugreifen  kapselt kritische Bereiche  zu jedem Zeitpunkt nur ein einziger Prozess im Monitor aktiv (Monitor-Prozedur ausführen)  Freigabe durch Verlassen der Monitor-Prozedur BS-I / Cl. Schnörr / HM Synchronisation / Mechanismen+Primitive 33 BS-I / Synchronisation / Mechanismen+Primitive Monitor (3)  Monitor-Konzept erinnert an  Kapselung: nur Zugriff über public-Prozeduren wait( disc_access ); // Daten lesen signal( disc_access ); 36 Was tun, wenn Prozess im Monitor blockieren muss ?  Condition-Variablen (Zustandsvariable): gleiches Beispiel: mit Monitor: Synchronisation / Mechanismen+Primitive Cl. Schnörr / HM  „Klasse“, bei der jede public-Methode synchronisiert ist wait( disc_access ); // Daten schreiben signal( disc_access ); BS-I /  Monitor wird von Programmiersprache / Compiler bereitgestellt  nicht der Programmierer ist für gegenseitigen Ausschluß verantwortlich Beispiel: Zugriff auf Festplatte mit Mutex: monitor disc { entry read( discaddr, memaddr ) { // Daten lesen }; entry write( discaddr, memaddr ) { //Daten schreiben }; init() { // Gerät initialisieren }; }; 34 Monitor (4)  Klassen (Objektorientierung)  Module mutex disc_access = 1; Cl. Schnörr / HM  Idee: Prozess muss auf Eintreten einer Bedingung (Condition) warten monitor disc;  m_wait( var ): aufrufenden Prozess sperren (er gibt den Monitor frei) disc.read( da, ma );  m_signal( var ): disc.write( da, ma );  gesperrte(n) Prozess(e) wecken  erfolgt unmittelbar vor Verlassen des Monitors Cl. Schnörr / HM 35 BS-I / Synchronisation / Mechanismen+Primitive Monitor (5) Process 1 Monitor Monitor (6) Prozess 2  Gesperrte Prozesse landen in Warteschlange zu entsprechender Condition-Variable  Interne Warteschlangen haben Vorrang vor Prozesszugriffen von außen Daten ConditionVariable Prozedur Prozedur BS-I / f() cv  Implementierung mit Mutex / Semaphor: g() f() { wait( cv); ... } m_wait(cv) Zeit t conditionVariable { g() { ... signal( cv); } Synchronisation / Mechanismen+Primitive wait() { m.lock(); queueSize++; m.release(); waiting.down(); } Cl. Schnörr / HM 37 BS-I / 38  Locking  erweitert Funktionalität von Mutexen  durch Unterscheidung verschiedener Lock-Modi (Zugriffsarten)  und Festlegung derer „Verträglichkeit“ entry append( item x ) { while (count == N-1) m_wait(nonfull); put(buffer, x); // count += 1; m_signal(nonempty); }  Concurrent Read: Lesezugriff, andere Schreiber erlaubt entry remove(item x) { while (count == 0) m_wait(nonempty); get(buffer, x); // count -= 1; m_signal(nonfull); } // Initialisierung } z.B. auf oberen Lock-Hierarchien in Datenbanken  Concurrent Write: Schreibzugriff, andere Schreiber erlaubt  Protected Read: Lesezugriff, andere Leser erlaubt, aber keine Schreiber (share lock)  Protected Write: Schreibzugriff, andere Leser erlaubt, aber kein weiterer Schreiber (update lock)  Exclusive: Lese/Schreibzugriff, keine anderen Zugriffe erlaubt  Thread fordert Lock in bestimmtem Modus an. Ist Lock-Modus zu einem Lock eines anderen Threads unverträglich -> Blockierung } Synchronisation / Mechanismen+Primitive Cl. Schnörr / HM Locking (1) monitor iostream { item buffer; int count; const int N = 64; condition nonempty, nonfull; BS-I / }; Synchronisation / Mechanismen+Primitive Erzeuger-Verbraucher-Problem mit Monitor init() { count = 0; } signal() { m.lock(); while( queueSize > 0 ) { // alle wecken queueSize--; waiting.up(); } m.release(); } int queueSize = 0; mutex m; semaphore waiting; m_signal(cv) Cl. Schnörr / HM 39 BS-I / Synchronisation / Mechanismen+Primitive Cl. Schnörr / HM 40 Locking (2) Lock-“Verträglichkeiten“: concurrent concurrent protected read write read protected exclusive write access sharing concurrent read read write + + + + - concurrent write write write + + - - - protected read read read + - + - - protected write write read + - - - - exclusive write -- - - - - - BS-I / Synchronisation / Mechanismen+Primitive Cl. Schnörr / HM Praxisbeispiele 41 BS-I / Praxis: Übersicht  Typ atomic_t (24 Bit Integer):  Initialisierung: atomic_t var = ATOMIC_INIT( 0 );  Atomare Operationen  auf Integer-Variablen  Bit-Operationen auf Bitvektoren  Spin-Locks  Reader-Writer-Locks  Wert setzen: atomic_set( &var, wert );  Addieren: atomic_add( wert, &var );  ++: atomic_inc( &var );  Subtrahieren: atomic_sub( wert, &var );  Semaphore / Reader-Writer Semaphore  Synchronisation in C++  Threads, Mutexe  atomare Befehle  Mutex-Objekt in C++ Synchronisation / Praxis 42 Praxis / Linux-Kernel: Atomare Integer-Operationen  Synchronisation im Linux-Kernel BS-I / Cl. Schnörr / HM Synchronisation / Praxis  --: atomic_dec( &var );  Auslesen: int i = atomic_read( &var );  res = atomic_sub_and_test( i, &var );  subtrahiert i atomar von var  return true, falls Ergebnis 0, sonst false Cl. Schnörr / HM 43 BS-I / Synchronisation / Praxis / Linux-Kernel  res = atomic_add_negative( i, &var );  addiert i atomar zu var  return true, falls Ergebnis negativ, sonst false Cl. Schnörr / HM 44 Praxis / Linux-Kernel: Atomare Bit-Operationen Praxis / Linux-Kernel: Spin-Locks (1)  Einzelne Bits in Bitvektoren setzen  Spin-Lock:  Datentyp: beliebig, z.B. unsigned long bitvektor = 0;  Lock mit Mutex-Funktion: gegenseitiger Ausschluß  nur über Pointer anzusprechen  Anzahl der nutzbaren Bits abhängig vom verwendeten Datentyp  Test-and-Set-Operationen geben zusätzlich vorherigen Wert des jeweiligen Bits zurück  set_bit( i, &bv );  clear_bit( i, &bv );  change_bit( i, &bv ); i-tes Bit setzen i-tes Bil löschen i-tes Bit kippen  Code, der Spin-Lock anfordert und nicht erhält  blockiert nicht (kein aufwendiger Wechsel in Kernel-Mode)  sondern läuft weiter (spinning), bis Lock verfügbar  b = test_and_set_bit( i, &bv );  b = test_and_clear_bit( i, &bv );  b = test_and_change_bit( i, &bv );  nur zu verwenden  bei kurzen „Wartezeiten“ auf Lock,  bei Mehrprozessorsystemen  Einzelne Bits auslesen:  b = test_bit( i, &bv );  sind nicht „rekursiv“, also z.B. nicht in rekursiven Funktionen verwendbar  Suchfunktionen:  Typ spinlock_t spin_lock( &slock ): /* kritischer Abschnitt */ spin_unlock( &slock );  pos = find_first_bit( &bv, length );  pos = find_first_zero_bit( &bv, length ); BS-I / Cl. Schnörr / HM Synchronisation / Praxis / Linux-Kernel 45 BS-I / Praxis / Linux-Kernel: Spin-Locks (2) Cl. Schnörr / HM Synchronisation / Praxis / Linux-Kernel 46 Praxis / Linux-Kernel: Reader-Writer-Locks  da Spin-Locks nicht schlafen/blockieren, sind diese in Interrupt-Handlern verwendbar  Reader-Writer-Locks:  in diesem Fall: zusätzlich Interrupts sperren:  Alternative zu normalen Locks, die mehrere Lesezugriffe zulässt, spinlock_t slock = SPIN_LOCK_UNBLOCKED; unsigned long flags; spin_lock_irqsave( &slock, flags ); /* ktitischer Abschnitt */ spin_unlock_irq_restore( &slock, flags ); spinlock_t slock = SPIN_LOCK_UNLOCKED  aber bei schreibendem Zugriff exklusiv ist (wie normaler Lock)  // aktuelle Interrupts sichern // dann sperren // ursprüngl. Zustand restaurieren  wenn zu Beginn alle Interrupts aktiviert sind, geht es auch einfacher: spinlock_t slock = SPIN_LOCK_UNBLOCKED; spin_lock_irq( &slock ); /* ktitischer Abschnitt */ spin_unlock_irq( &slock ); BS-I / Synchronisation / Praxis / Linux-Kernel  auch Varianten für Interrupt-Behandlung: // aktuelle Interrupts sperren Cl. Schnörr / HM - read_lock_irq / read_unlock_irq - read_lock_irqsave / read_unlock_irqrestore 47 BS-I / Synchronisation / Praxis / Linux-Kernel - write_lock_irq / write_unlock_irq - write_lock_irw_save / write_unlock_irqrestore Cl. Schnörr / HM 48 Praxis / Linux-Kernel: Semaphore (1) Praxis / Linux-Kernel: Semaphore (2)  Typ: semaphore  Kernel-Semaphore  sind „schlafende“ Locks  statische Deklaration: static DECLARE_SEMAPHORE_GENERIC( name, count ); static DECLARE_MUTEX( name );  wartende Prozesse werden in Warteschlange gestellt, bei Freigabe wird erster geweckt  eignen sich für Sperren, die über längeren Zeitraum gehalten werden (<-> Spin-Lock) // count = 1  dynamische Deklaration:  sind nur im Prozess-Kontext, nicht in Interrupt-Handlern einsetzbar (Interrupt-Handler werden nicht vom Scheduler behandelt) sema_init( &sem, count ); init_MUTEX( &sem );  Code, der Semaphore verwendet, darf nicht bereits normalen Lock besitzen (Semaphore-Zugriff kann zum „schlafen-legen“ führen // count = 1;  Verwendung mit up() und down(): down( &sem ); /* kritischer bereich */ up( &sem ); BS-I / Cl. Schnörr / HM Synchronisation / Praxis / Linux-Kernel 49 BS-I / Synchronisation / Praxis / Linux-Kernel Praxis / Linux-Kernel: Semaphore (3) 50 Cl. Schnörr / HM 52 Praxis: Synchronisation in C++ (1)  Reader-Writer-Semaphore:  analog zu Reader-Writer-Locks: Cl. Schnörr / HM  Threads, Mutexe, usw.: Typ rw_semaphore, der spezielle Up- und DownOperationen für Lese- und Schreibzugriffe erlaubt  pthreads-Bibliothek (UL-Threads):  quasi-Standard in Unix/Linux  wenig gebräuchlich unter Windows (<-> Win-API)  alle RW-Semaphore sind Mutexe (bei Initialisierung count=1) Lesender Code: Schreibender Code: #include , linken mit libpthreads static DECLARE_RWSEM( rwsem ); init_rwsem( &rwsem ); down_read( &rwsem ); //read-only Abschnitt up_read( &rwsem );  C++0x/C++11-Standard:  Thread/Mutex-API: down_write( &rwsem ); //lesen+schreiben-Abschnitt up_write( &rwsem ); #include  OpenMP-Standard:  Parallelisierung von Schleifen mittels Threads #include #pragma omp parallel for num_threads(NCPU) for ( long y = ylow; y <= yhigh; ++y ) {...} BS-I / Synchronisation / Praxis / Linux-Kernel Cl. Schnörr / HM 51 BS-I / Synchronisation / Praxis / C++ Praxis: Synchronisation in C++ (2) Praxis: Synchronisation in C++ (3) Nachbildung eines Monitors mittels pthreads struct ProducerConsumer { pthread_mutex_t count_mtx; //zu initialisieren pthread_cond_t full, empty; int count; ErzeugerVerbraucherprogramm void producer() { produce_item(); pc.enter(); } void remove() remove() { pthread_mutex_lock( pthread_mutex_lock( &count_mtx ); while ( count == 0 ) pthread_cond_wait( pthread_cond_wait( &empty, &count_mtx ); remove_item(); count--; if (count == N-1) pthread_cond_signal( pthread_cond_signal( &full ); pthread_mutex_unlock( pthread_mutex_unlock( &count_mtx ); } }; BS-I / Synchronisation / Praxis / C++  #pragma omp atomic newline statement_expression void consumer() { pc.remove(); consume_item(); }  #pragma omp critical { statements; ... }  Compiler-Unterstützung:  Cl. Schnörr / HM 53 Kontextwechsel vor return --> Vor Rückgabe Veränderung von refcount durch andere Threads möglich refcount; //not ok! }  Lösung: Mutex in Konstruktor und Destruktor class MutexObj { private: MutexObj( pthread_mutex_t & lock ) : _lock(lock) { pthread_mutex_lock( &_lock ); } ~MutexObj() { pthread_mutex_unlock( &_lock ); } pthread_mutex_t & / _lock; BS-I / Synchronisation Praxis / C++ }; __sync_lock_test_and_set( &lock, val );  InterlockedExchange( (long *)&lock, val );  Mutex-Objekt: return #include  OpenMP-Standard: Praxis: Synchronisation in C++ (4) int RefCount::dec_refcount() { pthread_mutex_lock( &lock ); --refcount; pthread_mutex_unlock( &lock );  C++0x/C++11-Standard: ProducerConsumer pc;  Atomic-API: void enter() enter() { pthread_mutex_lock( pthread_mutex_lock( &count_mtx ); while ( count == N-1 ) pthread_cond_wait( pthread_cond_wait( &full, &count_mtx ); enter_item(); count++; if ( count == 0 ) pthread_cond_signal( pthread_cond_signal( &empty ); pthread_mutex_unlock( pthread_mutex_unlock( &count_mtx ); } RefCount::refcount; //in mehreren Objekten  Atomare Befehle (--> Literatur): int RefCount::dec_refcount() { MutexObj lock( refcount_lock ); return --refcount; //ok } Synchronisation von refcount bis nach lokaler Kopie bei return Aber: nur ok bei return-per-value ! Cl. Schnörr / HM 55 BS-I / Synchronisation / Praxis / C++ // g++, testAndSet( _lock, 0 ); // msvc, , testAndSet( _lock, 0 ); Cl. Schnörr / HM 54