Synchronization refers to the time order in which threads access shared or global variables. Because several parallel threads may need to access or update the same shared variable at about the same time, explicit control of synchronization is often required. A program with faulty synchronization may compile and run without incident, but will produce incorrect results.
OpenMP provides a variety of synchronization constructs, explained in the following sections, that control how the execution of each thread proceeds relative to other team threads:
Section 9.7.1, “master construct” on page 349
Section 9.7.2, “critical construct” on page 349
Section 9.7.3, “barrier directive” on page 350
Section 9.7.4, “atomic construct” on page 352
Section 9.7.5, “flush directive” on page 352
Section 9.7.6, “ordered construct” on page 353
The master directive identifies a construct that specifies a structured block that is executed by the master thread of the team. The syntax of the master directive is as follows:
#pragma omp master new-line structured-block
Other threads in the team do not execute the associated structured block. There is no implied barrier either on entry to or exit from the master construct.
It is not allowed to branch into or out of master block.
The critical directive identifies a construct that restricts execution of the associated structured block to a single thread at a time. The syntax of the critical directive is as follows:
#pragma omp critical [(name)] new-line structured-block
An optional name may be used to identify the critical region. In the following sample code, all threads in the team will attempt to execute in parallel; however, because of the critical construct surrounding the increment of x, only one thread will be able to read/increment/write x at any time:
#include <stdlib.h> int main(int argc, char *argv[]) { int x = 0; #pragma omp parallel shared(x) { /* access allowed for one thread only at a time. */ #pragma omp critical x = x + 1; } /* end of pragma omp parallel shared. */ }
A small example program omp_reduction2.c, illustrating the use of the critical directive, is shown in 9.9.6, “reduction clause” on page 358.
The barrier directive synchronizes all the threads in a team. When executed, each thread in the team waits until all of the others have reached this point (see Figure 9-1). In this figure, the thread 2 has not been reached at the barrier statement; therefore, the other threads cannot proceed their execution flow.
The syntax of the barrier directive is as follows:
#pragma omp barrier new-line
After all threads in the team have executed the barrier, each thread in the team begins executing the statements after the barrier directive in parallel.
In the following program, though the printf() statement is inside the parallel construct, the value of x is printed only once after all the threads have finished incrementing the variable. It is because the inclusion of the barrier directive has made it possible:
/* File : omp_barrier.c */ #include <stdlib.h> int main(int argc, char *argv[]) { int x = 0; #pragma omp parallel shared(x) { #pragma omp critical x = x + 1; /* * The following block will be executed after all the threads finish * their work. */ #pragma omp barrier { #pragma omp single printf("The value of x is : %d ", x); } /* end of pragma omp barrier */ } /* end of pragma omp parallel shared. */ }
The output is printed as:
The value of x is : 3
If we remove the barrier construct from the code, we might not have achieved the expected results. The output would have been any of the following lines:
The value of x is : 1 The value of x is : 2 The value of x is : 3
depending upon the thread that gets a chance to execute the single block.
Note
Because the barrier directive does not have a C language statement as part of its syntax, there are some restrictions on its placement within a program. The example below illustrates these restrictions:
/* * ERROR - The barrier directive cannot be the immediate substatement of * an if statement. */ if (x != 0) #pragma omp barrier your_statement_line; ... /* OK - The barrier directive is enclosed in a compound statement. */ if (x != 0) { #pragma omp barrier your_statement_line; } ...
The atomic directive ensures that a specific memory location is updated atomically, rather than exposing it to the possibility of multiple, simultaneous writing threads.
The syntax of the atomic directive is as follows:
#pragma omp atomic new-line expression-stmt
The expression statement must have one of the following forms:
x binop=expr
x++
++x
x--
--x
Where:
x is an lvalue expression with scalar type.
expr is an expression with scalar type, and it does not reference the object designated by x.
binop is not an overloaded operator and is one of +, *, -, /, &, ^, |, <<, or >>.
Only the load and store of the object designated by x are atomic; the evaluation of expr is not atomic. To avoid race conditions, all updates of the location in parallel should be protected with the atomic directive, except those that are known to be free of race conditions.
The flush directive identifies a synchronization point at which the implementation must provide a consistent view of memory. Thread-visible variables are written back to memory at this point. The syntax of the flush directive is as follows:
#pragma omp flush [(variable-list)] new-line
The optional variable list contains a list of named variables that will be flushed in order to avoid flushing all variables. For pointers in the list, note that the pointer itself is flushed, not the object it references to.
Note that because the flush directive does not have a C language statement as part of its syntax, there are some restrictions on its placement within a program. The example below illustrates these restrictions:
/* * ERROR - The flush directive cannot be the immediate substatement of * an if statement. */ if (x != 0) #pragma omp flush (x) your_statement_line; ... /* OK - The flush directive is enclosed in a compound statement. */ if (x != 0) { #pragma omp flush (x) your_statement_line; } ...
A variable specified in a flush directive must not have a reference type provided by the C++ language. The flush directive only provides consistency between the executing thread and global memory. To achieve a globally consistent view across all threads, each thread must execute a flush operation.
The ordered directive specifies that iterations of the enclosed loop will be executed in the same order as if they were executed on a serial processor. The syntax of the ordered directive is as follows:
#pragma omp ordered new-line structured-block
An ordered directive can only appear in the dynamic extent of the for or parallel for directive. Only one thread is allowed in an ordered section at any time. It is illegal to branch into or out of an ordered block. A loop which contains an ordered directive must be a loop with an ordered clause. The following code fragment illustrates the use of the ordered directive:
#pragma omp for ordered for (i = 0; i <n; i++) { ... if (i <= 10) { ... #pragma omp ordered { ... } } ... if (i > 10) { ... #pragma omp ordered { ... } } ... }