Freigeben über


2. Direktiven

Anweisungen basieren auf #pragma-Anweisungen, die in den C- und C++-Standards definiert sind. Compiler, die die OpenMP-API für C- und C++ unterstützen, enthalten eine Befehlszeilenoption, die die Interpretation aller OpenMP-Compileranweisungen aktiviert und ermöglicht.

2.1 Anweisungsformat

Die Syntax einer OpenMP-Anweisung wird formell durch die Grammatik in Anhang C und informell wie folgt angegeben:

#pragma omp directive-name  [clause[ [,] clause]...] new-line

Jede Anweisung beginnt mit #pragma omp, um das Konfliktpotenzial mit anderen Pragmaanweisungen (Nicht-OpenMP oder Herstellererweiterungen für OpenMP) mit denselben Namen zu reduzieren. Der Rest der Anweisung folgt den Konventionen der C- und C++-Standards für Compileranweisungen. Insbesondere können Leerzeichen vor und nach # verwendet werden, und manchmal müssen Leerzeichen verwendet werden, um die Wörter in einer Anweisung zu trennen. Für Vorverarbeitungstoken nach #pragma omp gelten Makroersetzungen.

Bei Anweisungen wird zwischen Groß- und Kleinschreibung unterschieden. Die Reihenfolge der Klauseln in Anweisungen ist nicht relevant. Klauseln für Anweisungen können nach Bedarf wiederholt werden, vorbehaltlich den in der Beschreibung der einzelnen Klauseln aufgeführten Einschränkungen. Wenn variable-list in einer Klausel vorkommt, darf die Liste nur Variablen angeben. Pro Anweisung kann nur ein directive-name angegeben werden. Beispielsweise ist die folgende Anweisung nicht zulässig:

/* ERROR - multiple directive names not allowed */
#pragma omp parallel barrier

Eine OpenMP-Anweisung gilt für höchstens eine nachfolgende Anweisung, die ein strukturierter Block sein muss.

2.2 Bedingte Kompilierung

Der _OPENMP-Makroname wird durch OpenMP-kompatible Implementierungen als dezimale Konstante yyyymm definiert. Dies ist das Jahr und der Monat der genehmigten Spezifikation. Dieses Makro darf nicht Gegenstand einer #define- oder einer #undef-Vorverarbeitungsanweisung sein.

#ifdef _OPENMP
iam = omp_get_thread_num() + index;
#endif

Wenn Anbieter Erweiterungen für OpenMP definieren, können sie zusätzliche vordefinierte Makros angeben.

2.3 parallel-Konstrukt

Die folgende Anweisung definiert einen parallelen Bereich. Dabei handelt es sich um einen Bereich des Programms, der von vielen Threads parallel ausgeführt werden soll. Diese Anweisung ist das grundlegende Konstrukt, das die parallele Ausführung startet.

#pragma omp parallel [clause[ [, ]clause] ...] new-line   structured-block

clause hat einen der folgenden Werte:

  • if( scalar-expression )
  • private( variable-list )
  • firstprivate( variable-list )
  • default(shared | none)
  • shared( variable-list )
  • copyin( variable-list )
  • reduction( operator : variable-list )
  • num_threads( integer-expression )

Wenn ein Thread ein parallel-Konstrukt erreicht, wird ein Team von Threads erstellt, falls einer der folgenden Fälle zutrifft:

  • Es ist keine if-Klausel vorhanden.
  • Der if-Ausdruck ergibt einen Wert ungleich null.

Dieser Thread wird zum Masterthread des Teams mit der Threadnummer 0, und alle Threads im Team, einschließlich des Masterthreads, führen den Bereich parallel aus. Wenn der Wert des if-Ausdrucks null ist, wird der Bereich serialisiert.

Um die Anzahl der angeforderten Threads zu ermitteln, werden die folgenden Regeln in der angegebenen Reihenfolge berücksichtigt. Die erste Regel, deren Bedingung erfüllt ist, wird angewendet:

  1. Wenn die num_threads-Klausel vorhanden ist, ist der Wert des ganzzahligen Ausdrucks die Anzahl der angeforderten Threads.

  2. Wenn die omp_set_num_threads-Bibliotheksfunktion aufgerufen wurde, ist der Wert des Arguments im zuletzt ausgeführten Aufruf die Anzahl der angeforderten Threads.

  3. Wenn die Umgebungsvariable OMP_NUM_THREADS definiert ist, ist der Wert dieser Umgebungsvariablen die Anzahl der angeforderten Threads.

  4. Wenn keine der oben genannten Methoden verwendet wird, ist die Anzahl der angeforderten Threads implementierungsdefiniert.

Wenn die num_threads-Klausel vorhanden ist, ersetzt sie die Anzahl der Threads, die von der omp_set_num_threads-Bibliotheksfunktion oder der OMP_NUM_THREADS-Umgebungsvariablen angefordert werden, nur für den parallelen Bereich, auf den sie angewendet wird. Spätere parallele Bereiche sind davon nicht betroffen.

Die Anzahl der Threads, die den parallelen Bereich ausführen, hängt auch davon ab, ob die dynamische Anpassung der Anzahl der Threads aktiviert ist. Wenn die dynamische Anpassung deaktiviert ist, führt die angeforderte Anzahl von Threads den parallelen Bereich aus. Wenn die dynamische Anpassung aktiviert ist, ist die angeforderte Anzahl von Threads die maximale Anzahl von Threads, die den parallelen Bereich ausführen können.

Wenn ein paralleler Bereich erkannt wird, während die dynamische Anpassung der Anzahl der Threads deaktiviert ist, und die Anzahl der für den parallelen Bereich angeforderten Threads größer ist als die Anzahl, die das Laufzeitsystem bereitstellen kann, ist das Verhalten des Programms implementierungsdefiniert. Eine Implementierung kann z. B. die Ausführung des Programms unterbrechen oder den parallelen Bereich serialisieren.

Die omp_set_dynamic-Bibliotheksfunktion und die OMP_DYNAMIC-Umgebungsvariable können verwendet werden, um die dynamische Anpassung der Anzahl von Threads zu aktivieren und zu deaktivieren.

Die Anzahl der physischen Prozessoren, die die Threads zu einem bestimmten Zeitpunkt hosten, ist implementierungsdefiniert. Nach der Erstellung bleibt die Anzahl der Threads im Team für die Dauer der Ausführung dieses parallelen Bereichs konstant. Sie kann entweder explizit vom Benutzer oder automatisch vom Laufzeitsystem von einem parallelen Bereich in einen anderen geändert werden.

Die Anweisungen, die innerhalb des dynamischen Umfangs des parallelen Bereichs enthalten sind, werden von jedem Thread ausgeführt, und jeder Thread kann einen Pfad von Anweisungen ausführen, der sich von den anderen Threads unterscheidet. Anweisungen außerhalb des lexikalischen Umfangs eines parallelen Bereichs werden als verwaiste Anweisungen bezeichnet.

Am Ende eines parallelen Bereichs gibt es eine implizierte Barriere. Nur der Masterthread des Teams setzt die Ausführung am Ende eines parallelen Bereichs fort.

Wenn ein Thread in einem Team, das einen parallelen Bereich ausführt, auf ein weiteres parallel-Konstrukt trifft, erstellt er ein neues Team und wird zum Master dieses neuen Teams. Geschachtelte parallele Bereiche werden standardmäßig serialisiert. Daher wird standardmäßig ein geschachtelter paralleler Bereich von einem Team ausgeführt, das aus einem Thread besteht. Das Standardverhalten kann mithilfe der Laufzeitbibliotheksfunktion omp_set_nested oder der Umgebungsvariablen OMP_NESTED geändert werden. Die Anzahl der Threads in einem Team, die einen geschachtelten parallelen Bereich ausführen, ist jedoch implementierungsdefiniert.

Die Einschränkungen der parallel-Anweisung lauten wie folgt:

  • Es kann höchstens eine if-Klausel in der Anweisung vorkommen.

  • Es wird nicht angegeben, ob Nebeneffekte innerhalb des if-Ausdrucks oder num_threads-Ausdrucks auftreten.

  • Ein throw, der in einem parallelen Bereich ausgeführt wird, muss dazu führen, dass die Ausführung innerhalb des dynamischen Umfangs desselben strukturierten Blocks fortgesetzt wird, und er muss vom gleichen Thread abgefangen werden, der die Ausnahme ausgelöst hat.

  • Nur eine einzelne num_threads-Klausel kann in der Anweisung auftreten. Der Ausdruck num_threads wird außerhalb des Kontexts des parallelen Bereichs ausgewertet und muss als positiver ganzzahliger Wert ausgewertet werden.

  • Die Reihenfolge der Auswertung der if- und num_threads-Klausel ist nicht angegeben.

Querverweise

2.4 Arbeitsteilungskonstrukte

Ein Arbeitsteilungskonstrukt verteilt die Ausführung der zugeordneten Anweisung unter den Membern des Teams, die darauf stoßen. Die Arbeitsteilungsanweisungen starten keine neuen Threads, und es gibt keine implizite Barriere beim Eintritt in ein Arbeitsteilungskonstrukt.

Die Sequenz von Arbeitsteilungskonstrukten und erkannten barrier-Anweisungen muss für jeden Thread in einem Team identisch sein.

OpenMP definiert die folgenden Arbeitsteilungskonstrukte, und diese Konstrukte werden in den folgenden Abschnitten beschrieben:

2.4.1 for-Konstrukt

Die for-Anweisung identifiziert ein iteratives Arbeitsteilungskonstrukt, das angibt, dass die Iterationen der zugeordneten Schleife parallel ausgeführt werden. Die Iterationen der for-Schleife werden über Threads verteilt, die bereits in dem Team vorhanden sind, das das parallel-Konstrukt, an das es gebunden ist, ausführt. Die Syntax des for-Konstrukts lautet wie folgt:

#pragma omp for [clause[[,] clause] ... ] new-line for-loop

Die Klausel ist eine der folgenden:

  • private( variable-list )
  • firstprivate( variable-list )
  • lastprivate( variable-list )
  • reduction( operator : variable-list )
  • ordered
  • schedule( kind [, chunk_size] )
  • nowait

Die for-Anweisung legt Einschränkungen für die Struktur der entsprechenden for-Schleife fest. Insbesondere muss die entsprechende for-Schleife kanonische Form aufweisen:

for ( init-expr ; var logical-op b ; incr-expr )

init-expr
Einer der folgenden:

  • var = lb
  • integer-type var = lb

incr-expr
Einer der folgenden:

  • ++ var
  • var ++
  • -- var
  • var --
  • var += incr
  • var -= incr
  • var = var + incr
  • var = incr + var
  • var = var - incr

var
Eine Ganzzahlvariable mit Vorzeichen. Wenn diese Variable andernfalls freigegeben würde, wird sie implizit für die Dauer von for als privat festgelegt. Ändern Sie diese Variable nicht im Textkörper der for-Anweisung. Sofern die Variable nicht als lastprivate angegeben ist, ist der Wert nach der Schleife unbestimmt.

logical-op
Einer der folgenden:

  • <
  • <=
  • >
  • >=

lb, b und incr
Schleife für invariante Ganzzahlausdrücke. Während der Auswertung dieser Ausdrücke gibt es keine Synchronisierung, sodass alle ausgewerteten Nebeneffekte unbestimmte Ergebnisse erzeugen.

Die kanonische Form ermöglicht, dass die Anzahl der Schleifeniterationen beim Eintritt in die Schleife berechnet wird. Diese Berechnung wird mit Werten vom Typ var nach ganzzahligen Hochstufungen durchgeführt. Wenn insbesondere der Wert von b - lb + incr nicht in diesem Typ dargestellt werden kann, ist das Ergebnis unbestimmt. Wenn außerdem logical-op < oder <= ist, muss incr-expr auslösen, dass var für jede Iteration der Schleife erhöht wird. Wenn logical-op > oder >= ist, muss incr-expr auslösen, dass var für jede Iteration der Schleife verringert wird.

Die schedule-Klausel gibt an, wie Iterationen der for-Schleife auf Threads des Teams aufgeteilt werden. Die Richtigkeit eines Programms darf nicht davon abhängen, welcher Thread eine bestimmte Iteration ausführt. Der Wert von chunk_size, falls angegeben, muss ein invarianter ganzzahliger Schleifenausdruck mit einem positiven Wert sein. Während der Auswertung dieses Ausdrucks gibt es keine Synchronisierung, sodass alle ausgewerteten Nebeneffekte unbestimmte Ergebnisse erzeugen. kind für den Zeitplan kann einen der folgenden Werte aufweisen:

Tabelle 2-1: kind-Werte für schedule-Klausel

Wert Beschreibung
static Wenn schedule(static, chunk_size ) angegeben wird, werden Iterationen in Abschnitte einer Größe unterteilt, die durch chunk_size angegeben wird. Die Blöcke werden in der Reihenfolge der Threadnummer und im Roundrobin-Verfahren statisch Threads im Team zugewiesen. Wenn kein Wert für chunk_size angegeben wird, wird der Iterationsraum in Blöcke unterteilt, die ungefähr gleich groß sind, wobei jedem Thread ein Abschnitt zugewiesen ist.
dynamisch Wenn schedule(dynamic, chunk_size ) angegeben wird, werden die Iterationen in eine Reihe von Blöcken unterteilt, die jeweils chunk_size Iterationen enthalten. Jeder Block wird einem Thread zugewiesen, der auf eine Zuordnung wartet. Der Thread führt den Block von Iterationen aus und wartet dann auf die nächste Zuordnung, bis keine Blöcke für die Zuweisung mehr vorhanden sind. Der letzte zuzuweisende Teil kann eine kleinere Anzahl von Iterationen aufweisen. Wenn chunk_size nicht angegeben ist, wird als Standardwert 1 verwendet.
guided Wenn schedule(guided, chunk_size ) angegeben wird, werden die Iterationen Threads in Blöcken mit abnehmenden Größen zugewiesen. Wenn ein Thread seinen zugewiesenen Block von Iterationen beendet, wird er dynamisch einem anderen Block zugewiesen, bis keiner übrig ist. Bei einem chunk_size-Wert von 1 ist die Größe jedes Blocks ungefähr die Anzahl der nicht zugewiesenen Iterationen dividiert durch die Anzahl der Threads. Diese Größen sinken fast exponentiell auf 1. Bei chunk_size mit einem k-Wert größer als 1 verringern sich die Größen fast exponentiell auf k, außer dass der letzte Block weniger als k Iterationen aufweisen kann. Wenn chunk_size nicht angegeben ist, wird als Standardwert 1 verwendet.
runtime Wenn schedule(runtime) angegeben wird, wird die Entscheidung zur Planung bis zur Laufzeit zurückgestellt. kind für den Zeitplan und die Größe der Blöcke kann zur Laufzeit ausgewählt werden, indem die Umgebungsvariable OMP_SCHEDULE festgelegt wird. Wenn diese Umgebungsvariable nicht festgelegt ist, ist der resultierende Zeitplan implementierungsdefiniert. Wenn schedule(runtime) angegeben ist, darf chunk_size nicht angegeben werden.

Wenn keine explizit definierte schedule-Klausel vorhanden ist, ist der Standard-schedule implementierungsdefiniert.

Ein OpenMP-kompatibles Programm sollte nicht auf einen bestimmten Zeitplan für die korrekte Ausführung angewiesen sein. Ein Programm sollte sich nicht darauf verlassen, dass kind für einen Zeitplan genau der oben angegebenen Beschreibung entspricht, da es möglich ist, Variationen in den Implementierungen desselben Zeitplan-kind über verschiedene Compiler hinweg zu haben. Die Beschreibungen können verwendet werden, um den Zeitplan auszuwählen, der für eine bestimmte Situation geeignet ist.

Die ordered-Klausel muss vorhanden sein, wenn ordered-Anweisungen an das for-Konstrukt gebunden sind.

Am Ende eines for-Konstrukts gibt es eine implizite Barriere, es sei denn, eine nowait-Klausel wird angegeben.

Die Einschränkungen der for-Anweisung lauten wie folgt:

  • Die for-Schleife muss ein strukturierter Block sein, und darüber hinaus darf die Ausführung nicht durch eine break-Anweisung beendet werden.

  • Die Werte der Schleifensteuerungsausdrücke der for-Schleife, die einer for-Anweisung zugeordnet ist, müssen für alle Threads im Team identisch sein.

  • Diefor-Schleifeniterationsvariable muss einen signierten ganzzahligen Typ aufweisen.

  • Nur eine einzelne schedule-Klausel kann in einer for-Anweisung auftreten.

  • Nur eine einzelne ordered-Klausel kann in einer for-Anweisung auftreten.

  • Nur eine einzelne nowait-Klausel kann in einer for-Anweisung auftreten.

  • Es ist nicht angegeben, ob oder wie oft Nebeneffekte innerhalb der Ausdrücke chunk_size, lb, b oder incr auftreten.

  • Der Wert des chunk_size-Ausdrucks muss für alle Threads im Team identisch sein.

Querverweise

2.4.2 sections-Konstrukt

Die sections-Anweisung identifiziert ein nichtiteratives Arbeitsteilungskonstrukt, das eine Reihe von Konstrukten angibt, die auf die Threads in einem Team aufgeteilt werden sollen. Jeder Abschnitt wird einmal von einem Thread im Team ausgeführt. Die Syntax der sections-Anweisung lautet wie folgt:

#pragma omp sections [clause[[,] clause] ...] new-line
   {
   [#pragma omp section new-line]
      structured-block
   [#pragma omp section new-linestructured-block ]
...
}

Die Klausel ist eine der folgenden:

  • private( variable-list )
  • firstprivate( variable-list )
  • lastprivate( variable-list )
  • reduction( operator : variable-list )
  • nowait

Jedem Abschnitt wird eine section-Anweisung vorangestellt, wobei die section-Anweisung für den ersten Abschnitt optional ist. Die section-Anweisungen müssen im lexikalischen Umfang der sections-Anweisung erscheinen. Am Ende eines sections-Konstrukts gibt es eine implizite Barriere, es sei denn, nowait wird angegeben.

Die Einschränkungen der sections-Anweisung lauten wie folgt:

  • Eine section-Anweisung darf nicht außerhalb des lexikalischen Umfangs der sections-Anweisung erscheinen.

  • Nur eine einzelne nowait-Klausel kann in einer sections-Anweisung auftreten.

Querverweise

  • Klauseln private, firstprivate, lastprivate und reduction (Abschnitt 2.7.2)

2.4.3 single-Konstrukt

Die single-Anweisung identifiziert ein Konstrukt, das angibt, dass der zugeordnete strukturierte Block nur von einem Thread im Team ausgeführt wird (nicht unbedingt der Masterthread). Die Syntax der single-Anweisung lautet wie folgt:

#pragma omp single [clause[[,] clause] ...] new-linestructured-block

Die Klausel ist eine der folgenden:

  • private( variable-list )
  • firstprivate( variable-list )
  • copyprivate( variable-list )
  • nowait

Nach dem single-Konstrukt gibt es eine implizite Barriere, es sei denn, eine nowait-Klausel wird angegeben.

Die Einschränkungen der single-Anweisung lauten wie folgt:

  • Nur eine einzelne nowait-Klausel kann in einer single-Anweisung auftreten.
  • Die copyprivate-Klausel darf nicht mit der nowait-Klausel verwendet werden.

Querverweise

2.5 Kombinierte parallel-Arbeitsteilungskonstrukte

Kombinierte parallel-Arbeitsteilungskonstrukte sind Verknüpfungen zum Angeben eines parallelen Bereichs, der nur ein Arbeitsteilungskonstrukt aufweist. Die Semantik dieser Anweisungen ist identisch mit der expliziten Angabe einer parallel-Anweisung gefolgt von einem einzelnen Arbeitsteilungskonstrukt.

In den folgenden Abschnitten werden die kombinierten parallel-Arbeitsteilungskonstrukte beschrieben:

2.5.1 parallel for-Konstrukt

Die parallel for-Anweisung ist eine Verknüpfung für einen parallel-Bereich, der nur eine einzige for-Anweisung enthält. Die Syntax der parallel for-Anweisung lautet wie folgt:

#pragma omp parallel for [clause[[,] clause] ...] new-linefor-loop

Diese Anweisung erlaubt alle Klauseln der parallel-Anweisung und der for-Anweisung, mit Ausnahme der nowait-Klausel, mit identischen Bedeutungen und Einschränkungen. Die Semantik ist identisch mit der expliziten Angabe einer parallel-Anweisung, direkt gefolgt von einer for-Anweisung.

Querverweise

2.5.2 parallel sections-Konstrukt

Die parallel sections-Anweisung stellt eine Kurzform zum Angeben eines parallel-Bereichs bereit, der nur eine einzelne sections-Anweisung aufweist. Die Semantik ist identisch mit der expliziten Angabe einer parallel-Anweisung, direkt gefolgt von einer sections-Anweisung. Die Syntax der parallel sections-Anweisung lautet wie folgt:

#pragma omp parallel sections  [clause[[,] clause] ...] new-line
   {
   [#pragma omp section new-line]
      structured-block
   [#pragma omp section new-linestructured-block  ]
   ...
}

clause kann eine der Klauseln sein, die von der parallel-Anweisung und der sections-Anweisung akzeptiert werden, mit Ausnahme der nowait-Klausel.

Querverweise

2.6 master- und synchronization-Anweisungen

In den folgenden Abschnitten wird Folgendes beschrieben:

2.6.1 master-Konstrukt

Die master-Anweisung identifiziert ein Konstrukt, das einen strukturierten Block angibt, der vom Masterthread des Teams ausgeführt wird. Die Syntax der master-Anweisung lautet wie folgt:

#pragma omp master new-linestructured-block

Andere Threads im Team führen den zugeordneten strukturierten Block nicht aus. Es gibt keine implizierte Barriere beim Eintritt in oder Verlassen des Masterkonstrukts.

2.6.2 critical-Konstrukt

Die critical-Anweisung identifiziert ein Konstrukt, das die Ausführung des zugeordneten strukturierten Blocks auf jeweils einen einzelnen Thread einschränkt. Die Syntax der critical-Anweisung lautet wie folgt:

#pragma omp critical [(name)]  new-linestructured-block

Ein optionaler Name kann verwendet werden, um den kritischen Bereich zu identifizieren. Bezeichner, die zum Identifizieren eines kritischen Bereichs verwendet werden, weisen eine externe Verknüpfung auf und befinden sich in einem Namespace, der sich von den Namespaces unterschiedet, die von Bezeichnungen, Tags, Membern und normalen Bezeichnern verwendet werden.

Ein Thread wartet am Anfang eines kritischen Bereichs, bis kein anderer Thread einen kritischen Bereich (an einer beliebigen Stelle im Programm) mit demselben Namen ausführt. Alle nicht benannten critical-Anweisungen werden dem gleichen nicht angegebenen Namen zugeordnet.

2.6.3 barrier-Anweisung

Die barrier-Anweisung synchronisiert alle Threads in einem Team. Wenn ein Thread im Team darauf trifft, wartet er, bis alle anderen diesen Punkt erreicht haben. Die Syntax der barrier-Anweisung lautet wie folgt:

#pragma omp barrier new-line

Nachdem alle Threads im Team die barrier-Anweisung erreicht haben, beginnt jeder Thread im Team parallel mit der Ausführung der Anweisungen nach der barrier-Anweisung. Da die barrier-Anweisung keine C-Sprachanweisung als Teil ihrer Syntax aufweist, gibt es einige Einschränkungen bei der Platzierung innerhalb eines Programms. Weitere Informationen zur formalen Grammatik finden Sie in Anhang C. Das folgende Beispiel veranschaulicht diese Einschränkungen.

/* ERROR - The barrier directive cannot be the immediate
*          substatement of an if statement
*/
if (x!=0)
   #pragma omp barrier
...

/* OK - The barrier directive is enclosed in a
*      compound statement.
*/
if (x!=0) {
   #pragma omp barrier
}

2.6.4 atomic-Konstrukt

Die atomic-Anweisung stellt sicher, dass ein bestimmter Arbeitsspeicherort atomisch aktualisiert wird, anstatt ihn der Möglichkeit mehrerer gleichzeitiger Schreibthreads auszusetzen. Die Syntax der atomic-Anweisung lautet wie folgt:

#pragma omp atomic new-lineexpression-stmt

Die Ausdrucksanweisung muss eine der folgenden Formen aufweisen:

  • x binop = expr
  • x ++
  • ++ x
  • x --
  • -- x

In den vorhergehenden Ausdrücken gilt:

  • x ist ein lvalue-Ausdruck mit Skalartyp.

  • expr ist ein Ausdruck mit skalarem Typ und verweist nicht auf das Objekt, das durch xfestgelegt ist.

  • binop ist kein überladener Operator und ist +, *, -, /, &, ^, |, << oder >>.

Obwohl durch die Implementierung definiert ist, ob eine Implementierung alle atomic-Anweisungen durch critical-Anweisungen ersetzt, die denselben eindeutigen name-Wert haben, ermöglicht die atomic-Anweisung eine bessere Optimierung. Häufig stehen Hardwareanweisungen zur Verfügung, die das atomische Update mit dem geringsten Aufwand ausführen können.

Nur das Laden und Speichern des Objekts, das von x bezeichnet wird, sind atomisch; die Auswertung von expr ist nicht atomisch. Um Racebedingungen zu vermeiden, sollten alle Aktualisierungen des Speicherorts parallel mit der atomic-Anweisung geschützt werden, mit Ausnahme derjenigen, die als frei von Racebedingungen bekannt sind.

Die Einschränkungen der atomic-Anweisung lauten wie folgt:

  • Alle atomischen Verweise auf den Speicherort x im gesamten Programm müssen einen kompatiblen Typ haben.

Beispiele

extern float a[], *p = a, b;
/* Protect against races among multiple updates. */
#pragma omp atomic
a[index[i]] += b;
/* Protect against races with updates through a. */
#pragma omp atomic
p[i] -= 1.0f;

extern union {int n; float x;} u;
/* ERROR - References through incompatible types. */
#pragma omp atomic
u.n++;
#pragma omp atomic
u.x -= 1.0f;

2.6.5 flush-Anweisung

Die flush-Anweisung, ob explizit oder impliziert, gibt einen „threadübergreifenden“ Sequenzpunkt an, an dem die Implementierung sicherstellen muss, dass alle Threads in einem Team eine konsistente Ansicht bestimmter Objekte (unten angegeben) im Arbeitsspeicher haben. Dies bedeutet, dass frühere Auswertungen von Ausdrücken, die auf diese Objekte verweisen, abgeschlossen sind und nachfolgende Auswertungen noch nicht begonnen haben. Beispielsweise müssen Compiler die Werte der Objekte aus Registern im Arbeitsspeicher wiederherstellen, und die Hardware muss möglicherweise Schreibpuffer in den Arbeitsspeicher leeren und die Werte der Objekte aus dem Speicher neu laden.

Die Syntax der flush-Anweisung lautet wie folgt:

#pragma omp flush [(variable-list)]  new-line

Wenn alle Objekte, die eine Synchronisierung erfordern, durch Variablen festgelegt werden können, können diese Variablen in der optionalen variable-list angegeben werden. Wenn ein Zeiger in variable-list vorhanden ist, wird der Zeiger selbst geleert, nicht das Objekt, auf das sich der Zeiger bezieht.

Eine flush-Anweisung ohne variable-list synchronisiert alle freigegebenen Objekte mit Ausnahme nicht zugänglicher Objekte mit automatischer Speicherdauer. (Dies bedeutet wahrscheinlich mehr Aufwand als ein flush mit variable-list.) Eine flush-Anweisung ohne variable-list wird für die folgenden Anweisungen impliziert:

  • barrier
  • Bei Eintritt in oder Verlassen von critical
  • Bei Eintritt in oder Verlassen von ordered
  • Bei Eintritt in oder Verlassen von parallel
  • Bei Verlassen von for
  • Bei Verlassen von sections
  • Bei Verlassen von single
  • Bei Eintritt in oder Verlassen von parallel for
  • Bei Eintritt in oder Verlassen von parallel sections

Die Anweisung wird nicht impliziert, wenn eine nowait-Klausel vorhanden ist. Es ist darauf hinzuweisen, dass die flush-Anweisung für Folgendes nicht impliziert wird:

  • Bei Eintritt in for
  • Bei Eintritt in oder Verlassen von master
  • Bei Eintritt in sections
  • Bei Eintritt in single

Ein Verweis, der auf den Wert eines Objekts mit einem veränderlich qualifizierten Typ zugreift, verhält sich, als würde eine flush-Anweisung dieses Objekt am vorherigen Sequenzpunkt angeben. Ein Verweis, der den Wert eines Objekts mit einem veränderlich qualifizierten Typ ändert, verhält sich, als würde eine flush-Anweisung dieses Objekt am vorherigen Sequenzpunkt angeben.

Da die flush-Anweisung keine C-Sprachausweisung als Teil ihrer Syntax aufweist, gibt es einige Einschränkungen bei der Platzierung innerhalb eines Programms. Weitere Informationen zur formalen Grammatik finden Sie in Anhang C. Das folgende Beispiel veranschaulicht diese Einschränkungen.

/* ERROR - The flush directive cannot be the immediate
*          substatement of an if statement.
*/
if (x!=0)
   #pragma omp flush (x)
...

/* OK - The flush directive is enclosed in a
*      compound statement
*/
if (x!=0) {
   #pragma omp flush (x)
}

Die Einschränkungen der flush-Anweisung lauten wie folgt:

  • Eine Variable, die in einer flush-Anweisung angegeben wird, darf keinen Verweistyp aufweisen.

2.6.6 ordered-Konstrukt

Der strukturierte Block nach einer ordered-Anweisung wird in der Reihenfolge ausgeführt, in der Iterationen in einer sequenziellen Schleife ausgeführt würden. Die Syntax der ordered-Anweisung lautet wie folgt:

#pragma omp ordered new-linestructured-block

Eine ordered-Anweisung muss sich innerhalb des dynamischen Umfangs eines for- oder parallel for-Konstrukts befinden. Die for- oder parallel for-Anweisung, an die das ordered-Konstrukt gebunden wird, muss eine ordered-Klausel aufweisen, die wie in Abschnitt 2.4.1 beschrieben angegeben ist. Bei der Ausführung eines for- oder parallel for-Konstrukts mit einer ordered-Klausel werden ordered-Konstrukte streng in der Reihenfolge ausgeführt, in der sie in einer sequenziellen Ausführung der Schleife ausgeführt würden.

Die Einschränkungen der ordered-Anweisung lauten wie folgt:

  • Eine Iteration einer Schleife mit einem for-Konstrukt darf nicht mehr als einmal dieselbe geordnete Anweisung ausführen, und sie darf nicht mehr als eine ordered-Anweisung ausführen.

2.7 Datenumgebung

In diesem Abschnitt werden eine Anweisung und mehrere Klauseln zum Steuern der Datenumgebung während der Ausführung paralleler Bereiche dargestellt:

  • Eine threadprivate-Anweisung wird bereitgestellt, um file- scope-, namespace-scope- oder statische block-scope-Variablen für einen Thread als lokal festzulegen.

  • Klauseln, die für die Anweisungen angegeben werden können, um die Freigabeattribute von Variablen für die Dauer der parallelen oder Arbeitsteilungskonstrukte zu steuern, werden in Abschnitt 2.7.2 beschrieben.

2.7.1 threadprivate-Anweisung

Die threadprivate-Anweisung legt die benannten file- scope-, namespace-scope- oder statischen block-scope-Variablen in variable-list für einen Thread als privat fest. variable-list ist eine durch Trennzeichen getrennte Liste von Variablen, die keinen unvollständigen Typ aufweisen. Die Syntax der threadprivate-Anweisung lautet wie folgt:

#pragma omp threadprivate(variable-list) new-line

Jede Kopie einer threadprivate-Variablen wird einmal initialisiert, und zwar zu einem nicht angegebenen Punkt im Programm vor dem ersten Verweis auf diese Kopie und in der üblichen Weise (d. h. so wie die Masterkopie in einer seriellen Ausführung des Programms initialisiert würde). Wenn auf ein Objekt in einem expliziten Initialisierer einer threadprivate-Variablen verwiesen wird und der Wert des Objekts vor dem ersten Verweis auf eine Kopie der Variablen geändert wird, ist das Verhalten nicht angegeben.

Wie bei jeder privaten Variable darf ein Thread nicht auf die Kopie eines threadprivate-Objekts eines anderen Threads verweisen. In seriellen Bereichen und Masterbereichen des Programms beziehen sich Verweise auf die Objektkopie des Masterthreads.

Nachdem der erste parallele Bereich ausgeführt wurde, werden die Daten in den threadprivate-Objekten nur dann garantiert beibehalten, wenn der Mechanismus für dynamische Threads deaktiviert wurde und die Anzahl der Threads für alle parallelen Bereiche unverändert bleibt.

Die Einschränkungen der threadprivate-Anweisung lauten wie folgt:

  • Eine threadprivate-Anweisung für file-scope- oder namespace-scope-Variablen muss außerhalb einer Definition oder Deklaration vorhanden sein und muss lexikalisch allen Verweisen auf eine der Variablen in der Liste vorangestellt werden.

  • Jede Variable in variable-list einer threadprivate-Anweisung im Datei- oder Namespacebereich muss auf eine Variablendeklaration im Datei- oder Namespacebereich verweisen, die lexikalisch vor der Anweisung steht.

  • Eine threadprivate-Anweisung für statische block-scope-Variablen muss im Bereich der Variablen und nicht in einem geschachtelten Bereich auftreten. Die Anweisung muss lexikalisch allen Verweisen auf eine der Variablen in der Liste vorangehen.

  • Jede Variable in variable-list einer threadprivate-Anweisung im Blockbereich muss auf eine Variablendeklaration im selben Blockbereich verweisen, die lexikalisch vor der Anweisung steht. Eine Variablendeklaration muss den static-Speicherklassenbezeichner verwenden.

  • Wenn eine Variable in einer threadprivate-Anweisung in einer Übersetzungseinheit angegeben wird, muss sie in einer threadprivate-Anweisung in jeder Übersetzungseinheit angegeben werden, in der sie deklariert wird.

  • Eine threadprivate-Variable darf nicht in einer Klausel auftreten, mit Ausnahme der Klauseln copyin, copyprivate, schedule, num_threads und if.

  • Die Adresse einer threadprivate-Variablen ist keine Adresskonstante.

  • Eine threadprivate-Variable darf keinen unvollständigen Typ oder einen Verweistyp aufweisen.

  • Eine threadprivate-Variable mit nicht-POD-Klassentyp muss über einen zugänglichen, eindeutigen Kopierkonstruktor verfügen, wenn sie mit einem expliziten Initialisierer deklariert wird.

Im folgenden Beispiel wird veranschaulicht, wie das Ändern einer Variablen, die in einem Initialisierungsprogramm angezeigt wird, zu einem nicht angegebenen Verhalten führen kann und wie Sie dieses Problem mithilfe eines Hilfsobjekts und eines Kopierkonstruktors vermeiden können.

int x = 1;
T a(x);
const T b_aux(x); /* Capture value of x = 1 */
T b(b_aux);
#pragma omp threadprivate(a, b)

void f(int n) {
   x++;
   #pragma omp parallel for
   /* In each thread:
   * Object a is constructed from x (with value 1 or 2?)
   * Object b is copy-constructed from b_aux
   */
   for (int i=0; i<n; i++) {
      g(a, b); /* Value of a is unspecified. */
   }
}

Querverweise

2.7.2 Datenfreigabe-Attributklauseln

Mehrere Anweisungen akzeptieren Klauseln, mit denen ein Benutzer die Freigabeattribute von Variablen für die Dauer der Ausführung des Bereichs steuern kann. Freigabeattributklauseln gelten nur für Variablen im lexikalischen Umfang der Anweisung, in der die Klausel vorkommt. Nicht alle folgenden Klauseln sind für alle Anweisungen zulässig. Die Liste der Klauseln, die für eine bestimmte Anweisung gültig sind, werden mit der Anweisung beschrieben.

Wenn eine Variable sichtbar ist, wenn ein parallel-Konstrukt oder ein Arbeitsteilungskonstrukt gefunden wird, und die Variable nicht in einer Freigabeattributklausel oder threadprivate-Anweisung angegeben ist, wird die Variable freigegeben. Statische Variablen, die innerhalb des dynamischen Umfangs eines parallelen Bereichs deklariert werden, werden freigegeben. Heap-zugewiesener Arbeitsspeicher (z. B. mit malloc() in C oder C++ oder dem new-Operator in C++) wird freigegeben. (Der Zeiger auf diesen Arbeitsspeicher kann jedoch privat oder freigegeben sein.) Variablen mit automatischer Speicherdauer, die innerhalb des dynamischen Umfangs eines parallelen Bereichs deklariert werden, sind privat.

Die meisten Klauseln akzeptieren ein variable-list-Argument, bei dem es sich um eine durch Trennzeichen getrennte Liste von Variablen handelt, die sichtbar sind. Wenn eine Variable, auf die in einer Datenfreigabe-Attributklausel verwiesen wird, einen von einer Vorlage abgeleiteten Typ aufweist und keine anderen Verweise auf diese Variable im Programm vorhanden sind, ist das Verhalten nicht definiert.

Alle Variablen, die in Anweisungsklauseln vorkommen, müssen sichtbar sein. Klauseln können nach Bedarf wiederholt werden, aber keine Variable darf in mehr als einer Klausel angegeben werden, außer dass eine Variable sowohl in einer firstprivate- als auch in einer lastprivate-Klausel angegeben werden kann.

In den folgenden Abschnitten werden die Datenfreigabe-Attributklauseln beschrieben:

2.7.2.1 private

Die private-Klausel deklariert die Variablen in „variable-list“ als privat für jeden Thread in einem Team. Die Syntax der private-Klausel lautet wie folgt:

private(variable-list)

Das Verhalten einer Variablen, die in einer private-Klausel angegeben ist, lautet wie folgt. Für das Konstrukt wird ein neues Objekt mit automatischer Speicherdauer zugewiesen. Die Größe und Ausrichtung des neuen Objekts werden durch den Typ der Variablen bestimmt. Diese Zuordnung erfolgt einmal für jeden Thread im Team, und bei Bedarf wird ein Standardkonstruktor für ein Klassenobjekt aufgerufen. Andernfalls ist der Anfangswert unbestimmt. Das ursprüngliche Objekt, auf das von der Variablen verwiesen wird, weist beim Eintritt in das Konstrukt einen unbestimmten Wert auf, darf nicht innerhalb des dynamischen Umfangs des Konstrukts geändert werden und weist beim Verlassen des Konstrukts einen unbestimmten Wert auf.

Im lexikalischen Umfang des Anweisungskonstrukts verweist die Variable auf das neue private Objekt, das vom Thread zugeordnet wird.

Die Einschränkungen für die private-Klausel lauten wie folgt:

  • Eine Variable mit einem Klassentyp, der in einer private-Klausel angegeben wird, muss über einen zugänglichen, eindeutigen Standardkonstruktor verfügen.

  • Eine Variable, die in einer private-Klausel angegeben ist, darf keinen const-qualifizierten Typ aufweisen, es sei denn, sie verfügt über einen Klassentyp mit einem mutable-Member.

  • Eine in einer private-Klausel angegebene Variable darf keinen unvollständigen Typ oder einen Verweistyp aufweisen.

  • Variablen in der reduction-Klausel einer parallel-Anweisung können nicht in einer private-Klausel für eine Arbeitsteilungsanweisung angegeben werden, die an das parallel-Konstrukt gebunden ist.

2.7.2.2 firstprivate

Die firstprivate-Klausel stellt eine Obermenge der Funktionalität bereit, die von der private-Klausel bereitgestellt wird. Die Syntax der firstprivate-Klausel lautet wie folgt:

firstprivate(variable-list)

Variablen, die in variable-list angegeben werden, weisen die Semantik der private-Klausel auf, wie in Abschnitt 2.7.2.1 beschrieben. Die Initialisierung oder Konstruktion erfolgt so, als ob sie einmal pro Thread durchgeführt würde, bevor der Thread das Konstrukt ausführt. Bei einer firstprivate-Klausel für ein parallel-Konstrukt ist der Anfangswert des neuen privaten Objekts der Wert des ursprünglichen Objekts, das unmittelbar vor dem parallel-Konstrukt für den Thread vorhanden ist, der darauf stößt. Bei einer firstprivate-Klausel für ein Arbeitsteilungskonstrukt ist der Anfangswert des neuen privaten Objekts für jeden Thread, der das Arbeitsteilungskonstrukt ausführt, der Wert des ursprünglichen Objekts, das vor dem Zeitpunkt vorhanden ist, zu dem derselbe Thread auf das Arbeitsteilungskonstrukt trifft. Darüber hinaus wird für C++-Objekte das neue private Objekt für jeden Thread aus dem ursprünglichen Objekt als Kopie erstellt.

Die Einschränkungen für die firstprivate-Klausel lauten wie folgt:

  • Eine in einer firstprivate-Klausel angegebene Variable darf keinen unvollständigen Typ oder einen Verweistyp aufweisen.

  • Eine Variable mit einem Klassentyp, der als firstprivate angegeben wird, muss über einen zugänglichen, eindeutigen Kopierkonstruktor verfügen.

  • Variablen, die in einem parallelen Bereich privat sind oder in der reduction-Klausel einer parallel-Anweisung vorkommen, können nicht in einer firstprivate-Klausel für eine Arbeitsteilungsanweisung angegeben werden, die an das parallel-Konstrukt gebunden ist.

2.7.2.3 lastprivate

Die lastprivate-Klausel stellt eine Obermenge der Funktionalität bereit, die von der private-Klausel bereitgestellt wird. Die Syntax der lastprivate-Klausel lautet wie folgt:

lastprivate(variable-list)

Variablen, die in variable-list angegeben werden, weisen die Semantik der private-Klausel auf. Wenn eine lastprivate-Klausel in der Anweisung vorkommt, die ein Arbeitsteilungskonstrukt identifiziert, wird der Wert jeder lastprivate-Variablen aus der sequenziell letzten Iteration der zugeordneten Schleife oder der lexikalisch letzten Abschnittsanweisung dem ursprünglichen Objekt der Variablen zugewiesen. Variablen, denen von der letzten Iteration der for- oder parallel for-Anweisung oder vom lexikalisch letzten Abschnitt der sections- oder parallel sections-Anweisung kein Wert zugewiesen wird, weisen unbestimmte Werte nach dem Konstrukt auf. Nicht zugewiesene Unterobjekte weisen ebenfalls einen unbestimmten Wert nach dem Konstrukt auf.

Die Einschränkungen für die lastprivate-Klausel lauten wie folgt:

  • Alle Einschränkungen für private gelten.

  • Eine Variable mit einem Klassentyp, der als lastprivate angegeben wird, muss über einen zugänglichen, eindeutigen Kopierzuweisungsoperator verfügen.

  • Variablen, die in einem parallelen Bereich privat sind oder in der reduction-Klausel einer parallel-Anweisung vorkommen, können nicht in einer lastprivate-Klausel für eine Arbeitsteilungsanweisung angegeben werden, die an das parallel-Konstrukt gebunden ist.

2.7.2.4 shared

Diese Klausel gibt Variablen frei, die in variable-list für alle Threads in einem Team enthalten sind. Alle Threads innerhalb eines Teams greifen auf denselben Speicherbereich für shared-Variablen zu.

Die Syntax der shared-Klausel lautet wie folgt:

shared(variable-list)

2.7.2.5 default

Die default-Klausel ermöglicht es dem Benutzer, die Datenfreigabeattribute von Variablen zu beeinflussen. Die Syntax der default-Klausel lautet wie folgt:

default(shared | none)

Das Angeben von default(shared) entspricht der expliziten Auflistung jeder aktuell sichtbaren Variablen in einer shared-Klausel, es sei denn, sie ist threadprivate- oder const-qualifiziert. Wenn keine explizite default-Klausel vorhanden ist, entspricht das Standardverhalten dem Angeben von default(shared).

Die Angabe von default(none) erfordert, dass mindestens eine der folgenden Aussagen für jeden Verweis auf eine Variable im lexikalischen Umfang des parallel-Konstrukts wahr sein muss:

  • Die Variable wird explizit in einer Datenfreigabe-Attributklausel eines Konstrukts aufgeführt, das den Verweis enthält.

  • Die Variable wird innerhalb des parallel-Konstrukts deklariert.

  • Die Variable ist threadprivate.

  • Die Variable weist einen const-qualifizierten Typ auf.

  • Die Variable ist die Schleifensteuervariable für eine for-Schleife, die unmittelbar auf eine for- oder parallel for-Anweisung folgt, und der Variablenverweis tritt innerhalb der Schleife auf.

Wenn Sie eine Variable für eine firstprivate-, lastprivate- oder reduction-Klausel einer eingeschlossenen Anweisung angeben, wird ein impliziter Verweis auf die Variable im eingeschlossenen Kontext verursacht. Solche impliziten Verweise unterliegen auch den oben aufgeführten Anforderungen.

Es kann nur eine einzelne default-Klausel für eine parallel-Anweisung angegeben werden.

Das Standardattribut für die Datenfreigabe einer Variablen kann mithilfe der Klauseln private, firstprivate, lastprivate, reductionund shared überschrieben werden, wie im folgenden Beispiel gezeigt:

#pragma  omp  parallel  for  default(shared)  firstprivate(i)\
   private(x)  private(r)  lastprivate(i)

2.7.2.6 reduction

Diese Klausel führt eine Reduzierung der Skalarvariablen aus, die in variable-listangezeigt werden, mit dem Operator op. Die Syntax der reduction-Klausel lautet wie folgt:

reduction( op : variable-list )

Eine Reduzierung wird in der Regel für eine Anweisung in einer der folgenden Formen angegeben:

  • x = x op expr
  • x binop = expr
  • x = expr op x (außer Subtraktion)
  • x ++
  • ++ x
  • x --
  • -- x

Dabei gilt Folgendes:

x
Eine der in der Liste angegebenen Reduktionsvariablen.

variable-list
Eine durch Trennzeichen getrennte Liste von Skalarreduktionsvariablen.

expr
Ein Ausdruck mit Skalartyp, der nicht auf xverweist.

op
Kein überladener Operator, sondern +, *, -, &, ^, |, && oder ||.

binop
Kein überladener Operator, sondern +, *, -, &, ^ oder |.

Im Folgenden sehen Sie ein Beispiel für die reduction-Klausel:

#pragma omp parallel for reduction(+: a, y) reduction(||: am)
for (i=0; i<n; i++) {
   a += b[i];
   y = sum(y, c[i]);
   am = am || b[i] == c[i];
}

Wie im Beispiel gezeigt, kann ein Operator innerhalb eines Funktionsaufrufs versteckt werden. Der Benutzer sollte darauf achten, dass der in der reduction-Klausel angegebene Operator mit dem Reduktionsvorgang übereinstimmt.

Obwohl der rechte Operand des ||-Operators in diesem Beispiel keine Nebeneffekte hat, sind sie zulässig, sollten aber mit Bedacht verwendet werden. In diesem Zusammenhang kann ein Nebeneffekt, der während der sequenziellen Ausführung der Schleife sicher nicht auftreten kann, während der parallelen Ausführung auftreten. Zu diesem Unterschied kann es kommen, da die Reihenfolge der Ausführung der Iterationen unbestimmt ist.

Der Operator wird verwendet, um den Anfangswert aller privaten Variablen zu bestimmen, die vom Compiler für die Reduzierung verwendet werden, und um den Finalisierungsoperator zu bestimmen. Wenn Sie den Operator explizit angeben, kann die Reduktionsanweisung außerhalb des lexikalischen Umfangs des Konstrukts sein. Eine beliebige Anzahl von reduction-Klauseln kann für die Anweisung angegeben werden, eine Variable kann jedoch höchstens in einer reduction-Klausel für diese Anweisung angezeigt werden.

Eine private Kopie jeder Variablen in variable-list wird erstellt, eine für jeden Thread, als ob die private-Klausel verwendet wurde. Die private Kopie wird entsprechend dem Operator initialisiert (siehe die folgende Tabelle).

Am Ende des Bereichs, für den die reduction-Klausel angegeben wurde, wird das ursprüngliche Objekt aktualisiert, um das Ergebnis der Kombination seines ursprünglichen Werts mit dem Endwert jeder privaten Kopie mithilfe des angegebenen Operators widerzuspiegeln. Die reduction-Operatoren sind alle assoziativ (mit Ausnahme von Subtraktion), und der Compiler kann die Berechnung des endgültigen Werts frei neu zuordnen. (Die Teilergebnisse einer Subtraktionsreduktion werden hinzugefügt, um den Endwert zu bilden.)

Der Wert des ursprünglichen Objekts wird unbestimmt, wenn der erste Thread die enthaltende Klausel erreicht, und bleibt so, bis die Berechnung der Reduzierung abgeschlossen ist. Normalerweise wird die Berechnung am Ende des Konstrukts abgeschlossen sein. Wenn allerdings die reduction-Klausel für ein Konstrukt verwendet wird, auf das auch nowait angewendet wird, bleibt der Wert des ursprünglichen Objekts unbestimmt, bis eine barrier-Synchronisierung durchgeführt wurde, um sicherzustellen, dass alle Threads die reduction-Klausel abgeschlossen haben.

In der folgenden Tabelle sind die gültigen Operatoren und deren kanonische Initialisierungswerte aufgeführt. Der tatsächliche Initialisierungswert ist mit dem Datentyp der Reduktionsvariablen konsistent.

Operator Initialisierung
+ 0
* 1
- 0
& ~0
| 0
^ 0
&& 1
|| 0

Die Einschränkungen für die reduction-Klausel lauten wie folgt:

  • Der Typ der Variablen in der reduction-Klausel muss für den reduction-Operator gültig sein, außer dass Zeigertypen und Verweistypen niemals zulässig sind.

  • Eine Variable, die in der reduction-Klausel angegeben ist, darf nicht const-qualifiziert sein.

  • Variablen, die in einem parallelen Bereich privat sind oder in der reduction-Klausel einer parallel-Anweisung vorkommen, können nicht in einer reduction-Klausel für eine Arbeitsteilungsanweisung angegeben werden, die an das parallel-Konstrukt gebunden ist.

    #pragma omp parallel private(y)
    { /* ERROR - private variable y cannot be specified
                  in a reduction clause */
        #pragma omp for reduction(+: y)
        for (i=0; i<n; i++)
           y += b[i];
    }
    
    /* ERROR - variable x cannot be specified in both
                a shared and a reduction clause */
    #pragma omp parallel for shared(x) reduction(+: x)
    

2.7.2.7 copyin

Die copyin-Klausel bietet einen Mechanismus zum Zuweisen desselben Werts zu threadprivate-Variablen für jeden Thread im Team, der den parallelen Bereich ausführt. Für jede Variable, die in einer copyin-Klausel angegeben ist, wird der Wert der Variablen im Masterthread des Teams zu den Thread-privaten Kopien am Anfang des parallelen Bereichs kopiert, wie durch Zuweisung. Die Syntax der copyin-Klausel lautet wie folgt:

copyin(
variable-list
)

Die Einschränkungen für die copyin-Klausel lauten wie folgt:

  • Eine Variable, die in der copyin-Klausel angegeben wird, muss über einen zugänglichen, eindeutigen Kopierzuweisungsoperator verfügen.

  • Eine Variable, die in der copyin-Klausel angegeben ist, muss eine threadprivate-Variable sein.

2.7.2.8 copyprivate

Die copyprivate-Klausel stellt einen Mechanismus bereit, mit dem eine private Variable einen Wert von einem Member eines Teams an die anderen Member übertragen kann. Dies ist eine Alternative zur Verwendung einer freigegebenen Variablen für den Wert, wenn das Bereitstellen einer solchen freigegebenen Variable schwierig wäre (z. B. in einer Rekursion, die eine andere Variable auf jeder Ebene erfordert). Die copyprivate-Klausel kann nur in der single-Anweisung auftreten.

Die Syntax der copyprivate-Klausel lautet wie folgt:

copyprivate(
variable-list
)

Der Effekt der copyprivate-Klausel auf die Variablen in „variable-list“ tritt nach der Ausführung des strukturierten Blocks auf, der dem single-Konstrukt zugeordnet ist, und bevor einer der Threads im Team die Barriere am Ende des Konstrukts verlassen hat. Anschließend wird diese Variable in allen anderen Threads im Team für jede Variable in variable-list mit dem Wert der entsprechenden Variablen im Thread definiert (wie durch Zuweisung), der den strukturierten Block des Konstrukts ausgeführt hat.

Die Einschränkungen für die copyprivate-Klausel lauten wie folgt:

  • Eine Variable, die in der copyprivate-Klausel angegeben ist, darf nicht in einer private- oder firstprivate-Klausel für dieselbe single-Anweisung angezeigt werden.

  • Wenn eine single-Anweisung mit einer copyprivate-Klausel im dynamischen Umfang eines parallelen Bereichs auftritt, müssen alle Variablen, die in der copyprivate-Klausel angegeben sind, im einschließenden Kontext privat sein.

  • Eine Variable, die in der copyprivate-Klausel angegeben wird, muss über einen zugänglichen, eindeutigen Kopierzuweisungsoperator verfügen.

2.8 Anweisungsbindung

Die dynamische Bindung von Anweisungen muss den folgenden Regeln entsprechen:

  • Die Anweisungen for, sections, single, master und barrier werden an die dynamisch einschließende parallel gebunden, sofern vorhanden, unabhängig vom Wert einer if-Klausel, die in dieser Anweisung vorhanden sein kann. Wenn derzeit kein paralleler Bereich ausgeführt wird, werden die Anweisungen von einem Team ausgeführt, das nur aus dem Masterthread besteht.

  • Die ordered-Anweisung wird an die dynamisch umschließende for gebunden.

  • Die atomic-Anweisung erzwingt exklusiven Zugriff in Bezug auf atomic-Anweisungen in allen Threads, nicht nur im aktuellen Team.

  • Die critical-Anweisung erzwingt exklusiven Zugriff in Bezug auf critical-Anweisungen in allen Threads, nicht nur im aktuellen Team.

  • Eine Anweisung kann niemals an eine Anweisung gebunden werden, die sich außerhalb der am nächsten gelegenen dynamisch einschließenden parallel befindet.

2.9 Schachtelung von Anweisungen

Die dynamische Schachtelung von Anweisungen muss den folgenden Regeln entsprechen:

  • Eine parallel-Anweisung innerhalb einer anderen parallel erstellt logisch ein neues Team, das nur aus dem aktuellen Thread besteht, es sei denn, geschachtelte Parallelität ist aktiviert.

  • for-, sections- und single-Anweisungen, die an dasselbe parallel gebunden sind, dürfen nicht ineinander geschachtelt werden.

  • critical-Anweisungen mit demselben Namen dürfen nicht ineinander geschachtelt werden. Beachten Sie, dass diese Einschränkung nicht ausreicht, um Deadlock zu verhindern.

  • for-, sections- und single-Anweisungen sind im dynamischen Umfang von critical-, ordered- und master-Bereichen nicht zulässig, wenn die Anweisungen an dasselbe parallel wie die Bereiche gebunden sind.

  • barrier-Anweisungen sind im dynamischen Umfang von for-, ordered-, sections-, single-, master- und critical-Bereichen nicht zulässig, wenn die Anweisungen an dasselbe parallel wie die Bereiche gebunden sind.

  • master-Anweisungen sind im dynamischen Umfang von for-, sections- und single-Anweisungen nicht zulässig, wenn die master-Anweisungen an dasselbe parallel wie die Arbeitsteilungsanweisungen gebunden sind.

  • ordered-Anweisungen sind im dynamischen Umfang von critical-Bereichen nicht zulässig, wenn die Anweisungen an dasselbe parallel wie die Bereiche gebunden werden.

  • Jede Anweisung, die bei dynamischer Ausführung innerhalb eines parallelen Bereichs zulässig ist, ist auch zulässig, wenn sie außerhalb eines parallelen Bereichs ausgeführt wird. Wenn sie dynamisch außerhalb eines vom Benutzer angegebenen parallelen Bereichs ausgeführt wird, wird die Anweisung von einem Team ausgeführt, das nur aus dem Masterthread besteht.