9.7. Synchronization constructs

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:

9.7.1. master construct

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.

9.7.2. critical construct

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.

9.7.3. barrier directive

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.

Figure 9-1. Concept of barrier


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;
}
...


9.7.4. atomic construct

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.

9.7.5. flush directive

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.

9.7.6. ordered construct

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
        { ... }
    }
    ...
}

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset