Synchronisation Während in den parallelisierten Abschnitten eines OpenMP Programms die Threads die ihnen zugeteilten Berechnungen normalerweise voneinander unabhängig ausführen sollen, ist es manchmal notwendig, entweder die Reihenfolge festzulegen, in der die Threads gewisse Programmabschnitte abarbeiten, oder Haltepunkte einzubauen, an denen gewartet werden muß, bis alle Threads eine bestimmte Stelle im Code erreicht haben. Einen derartigen Synchronisationspunkt im engeren Sinn definert die barrier Direktive !$omp barrier bzw. #pragma omp barrier die in allen Threads einer Parallel Region vorkommen muß und bewirkt, daß kein Thread in der Ausführung des Programms fortfahren darf, bevor die anderen Mitglieder des Teams ebenfalls diese Stelle erreicht haben. Implizit findet eine solche Synchronisation der Threads am Ende jeder do/for, sections und single Direktive statt, kann aber dort durch die nowait Option verhindert werden. In diesem Fall wird es meist notwendig sein, die Threads später durch eine explizite barrier Direktive wieder zu synchronisieren. Soll eine Synchronisation “per Hand”, z.B. durch das Setzen und Testen globaler Variablen (Flags), vorgenommen werden, so dient die flush Direktive !$omp flush [(list)] bzw. #pragma omp flush [(list)] dazu, sicherzustellen, daß alle vor Ausführung der Direktive befindlichen Speicheroperationen tatsächlich abgeschlossen und noch keine der auf die Direktive folgenden begonnen wurden. Ist explizit eine Liste list von Variablen angegeben, so bezieht sich das nur auf die in dieser Liste angeführten Speicherstellen, sonst auf alle “shared” Variablen. Um einen Programmabschnitt vor dem gleichzeitigen Zugriff durch mehr als einen Thread zu schützen, steht die critical Direktive zur Verfügung, deren allgemeine Form !$omp critical [(lock)] critical block !$omp critical [(lock)] 12 bzw. #pragma omp critical [(lock)] critical block lautet. Sie bewirkt, daß der eingeschlossene Abschnitt zwar von allen Threads, aber immer nur von einem Thread auf einmal abgearbeitet wird. Gibt es in einem Programm verschiedene “kritische Abschnitte”, die einander logisch nicht ausschließen (und daher sehr wohl gleichzeitig ausgeführt werden können), so kann man sie durch verschiedene Namen (Locks) voneinander unterscheiden. Die Einschränkung der Ausführung besteht dann nur darin, daß nie mehr als ein Thread in jedem durch lock geschützten Abschnitt aktiv sein darf. Oft besteht ein kritischer Abschnitt nur aus einer einzigen Anweisung, durch die der Wert einer skalaren Variablen auf einfache Weise verändert wird. Da exklusiver Zugriff+Modifikation von Speicherstellen auf manchen Computerarchitekturen auf Hardwareniveau unterstützt wird (und daher von OpenMP dort nicht in Software emuliert werden sollte), steht für derartige Operationen als portabler Mechanismus die atomic Direktive !$omp atomic var=var operator expr oder !$omp atomic var=intrinsic(var,expr) bzw. #pragma omp atomic var=var operator expr; oder #pragma omp atomic var operator=expr; zur Verfügung, wobei in Fortran-90 für operator und intrinsic +,-,*,/,IAND,IOR,IEOR,.AND.,.OR.,.EQV.,.NEQV.,MAX,MIN und in C +,-,*,/,&,^,|,<<,>> 13 (dort aber auch Statements der Form var++; var--; usw.) in Frage kommen. Als Alternative zur critical Direktive besteht auch die Möglichkeit, den exklusiven Zugriff der Threads auf gemeinsame Variablen explizit durch Locks zu steuern.1 Dafür sind folgende Prozeduren der Laufzeitbibliothek vorgesehen Fortran-90 C omp_init_lock(lock) omp_destroy_lock(lock) omp_set_lock(lock) omp_unset_lock(lock) logical omp_test_lock(lock) void omp_init_lock(omp_lock_t *lock) void omp_destroy_lock(omp_lock_t *lock) void omp_set_lock(omp_lock_t *lock) void omp_unset_lock(omp_lock_t *lock) int omp_test_lock(omp_lock_t *lock) wobei in Fortran—je nach Betriebssystem—die Variable lock (Typ integer, der Name des Locks) genug Speicherplatz bieten muß, um eine Adresse aufnehmen zu können; in C ist lock ein Pointer vom Typ omp lock t, der in omp.h definiert ist. Die Prozeduren omp init lock und omp destroy lock erzeugen bzw. löschen ein Lock. Mit omp set lock kann ein Thread ein Lock setzen und sich damit z.B. den exklusiven Zugriff auf einen Speicherbereich reservieren (die übrigen Threads müssen währenddessen mit omp test lock prüfen, ob sie auf diese Daten zugreifen dürfen) und danach mit omp unset lock wieder freigeben. 1 Mutex=mutually exclusive synchronization. 14