A variable in an OpenMP parallel region can be either shared or private. If a variable is shared, then there exists one instance of this variable which is shared among all threads. If a variable is private, then each thread in a team of threads has its own local copy of the private variable.

In this article, we look how OpenMP specifies if a variable is shared or private.

Implicit Rules

OpenMP has a set of rules, which deduce the data-sharing attributes of variables.

For example, let us consider the following snippet of code.

int i = 0;
int n = 10;
int a = 7;

#pragma omp parallel for 
for (i = 0; i < n; i++)
{
    int b = a + i;
    ...
}

There are four variables i, n, a and b.

The data-sharing attribute of variables, which are declared outside the parallel region, is usually shared. Therefore, n and a are shared variables.

The loop iteration variables, however, are private by default. Therefore, i is private.

The variables which are declared locally within the parallel region are private. Thus b is private.

I recommend to declare the loop iteration variables inside the parallel region. In this case, it is clearer that this variables are private. The upper snippet of code then looks like:

int n = 10;                 // shared
int a = 7;                  // shared

#pragma omp parallel for 
for (int i = 0; i < n; i++) // i private
{
    int b = a + i;          // b private
    ...
}

Explicit rules

We can explicitly set the data-sharing attribute of a variable.

Shared

The shared(list) clause declares that all the variables in list are shared. In the next example

#pragma omp parallel for shared(n, a)
for (int i = 0; i < n; i++)
{
    int b = a + i;
    ...        
}

n and a are shared variables.

OpenMP does not put any restriction to prevent data races between shared variables. This is a responsibility of a programmer.

Shared variables introduce an overhead, because one instance of a variable is shared between multiple threads. Therefore, it is often best to minimize the number of shared variables when a good performance is desired.

Private

The private(list) clause declares that all the variables in list are private. In the next example

#pragma omp parallel for shared(n, a) private(b)
for (int i = 0; i < n; i++)
{
    b = a + i;
    ...
}

b is a private variable. When a variable is declared private, OpenMP replicates this variable and assigns its local copy to each thread.

The behavior of private variables is sometimes unintuitive. Let us assume that a private variable has a value before a parallel region. However, the value of the variable at the beginning of the parallel region is undefined. Additionally, the value of the variable is undefined also after the parallel region.

For example:

int p = 0; 
// the value of p is 0

#pragma omp parallel private(p)
{
    // the value of p is undefined
    p = omp_get_thread_num();
    // the value of p is defined
    ...
}
// the value of p is undefined

On several occasions, we can avoid listing private variables in the OpenMP constructs by declaring them inside a parallel region. For example, instead of

int p;
#pragma omp parallel private(p)
{
    p = omp_get_thread_num();
}

we can do

#pragma omp parallel
{
    int p = omp_get_thread_num();
    ...
}

I highly encourage declaring private variables inside a parallel region whenever possible. This guideline simplifies the code and increases its readability.

Default

There are two versions of the default clause. First, we focus on default(shared) option and then we consider default(none) clause.

These two versions are specific for C++ programmers of OpenMP. There are some additional default possibilities for Fortran programmers. For more details look at the OpenMP specification on the page 189.

Default (shared)

The default(shared) clause sets the data-sharing attributes of all variables in the construct to shared. In the following example

int a, b, c, n;
...

#pragma omp parallel for default(shared)
for (int i = 0; i < n; i++)
{
    // using a, b, c
}

a, b, c and n are shared variables.

Another usage of default(shared) clause is to specify the data-sharing attributes of the majority of the variables and then additionally define the private variables. Such usage is presented below:

int a, b, c, n;

#pragma omp parallel for default(shared) private(a, b)
for (int i = 0; i < n; i++)
{
    // a and b are private variables
    // c and n are shared variables 
}

Default (none)

The default(none) clause forces a programmer to explicitly specify the data-sharing attributes of all variables.

A distracted programmer might write the following piece of code

int n = 10;
std::vector<int> vector(n);
int a = 10;

#pragma omp parallel for default(none) shared(n, vector)
for (int i = 0; i < n; i++)
{
    vector[i] = i * a;
}

But then the compiler would complain

error: a not specified in enclosing parallel
         vector[i] = i * a;
                       ^
error: enclosing parallel
     #pragma omp parallel for default(none) shared(n, vector)
             ^

The reason for the unhappy compiler is that the programmer used default(none) clause and then she/he forgot to explicitly specify the data-sharing attribute of a. The correct version of the program would be

int n = 10;
std::vector<int> vector(n);
int a = 10;

#pragma omp parallel for default(none) shared(n, vector, a)
for (int i = 0; i < n; i++)
{
    vector[i] = i * a;
}

Good practices

I warmly encourage that a programmer follows the next two guidelines.

The first guideline is to always write parallel regions with the default(none) clause. This forces the programmer to explicitly think about the data-sharing attributes of all variables.

The second guideline is to declare private variables inside parallel regions whenever possible. This guideline improves the readability of the code and makes it clearer.

Summary

We studied the implicit and explicit rules for deducing the data-sharing attributes of variables. We also recommended a couple of good practices that a programmer should follow.

Links:

Jaka’s Corner OpenMP series: