I'm just beginning to learn to use OpenMP. I'm curious about when one would use the single work sharing construct and when one would use the atomic clause.
From what I've read so far, the atomic clause seems redundant. The single construct denotes a block of code that is executed by only one thread (not necessarily the master thread) and the atomic clause provides mutual exclusion but only applies to the update of a memory location (the update of X in the following example).
Despite the extra condition placed on the atomic clause (that it can only be used in update situations), it seems like the single construct is capable of achieving everything the atomic clause can.
I know I'm missing something, can someone please motivate the existence of the atomic clause ?
The two directives are completely different.
single
does not provide any mechanism related to mutual exclusion or instruction atomic. As a result, a single
section can work on a shared variable modified by other threads. Your are confused because you probably assumes that others threads are not doing anything during a single
section. This is wrong. Indeed, threads could finish a parallel for
(or another single
section) with a nowait
close or could also create OpenMP tasks executed during the single
section, etc.
Here is an example:
#pragma omp parallel
{
...
// This section does not wait for others threads
#pragma omp master
{
// some computation
int a = rand();
// some computation
}
// This section wait for others threads only at the end
#pragma omp single
{
// some computation
int b = rand();
// some computation
}
...
}
In the code above, the two rand
calls can be executed in parallel (because the master
section can be executed by one thread while another thread run the single
section).
Since rand
is not thread safe, this can cause undefined results (probably a race condition). Moreover, please note that using a single
directive does not make the rand
call atomic.
In order to avoid race conditions, you can use synchronization directives such as barrier
or taskwait
. You can also use mutual exclusion using critical
or atomic instructions using atomic
.
Synchronizations directives should be avoided (when it is possible) as they often cause scalability issues. atomic
instructions can only be applied on specific cases. For example, you cannot use atomic
directives on the rand
calls in the code above. However, you can use critical
sections to protect these calls (while keeping the single
and master
sections that can be executed in parallel).
atomic
should be preferred over critical
when both make sense because the overhead of atomic
instructions is generally smaller.
An atomic
directive cannot be replaced by only a single
directive because the atomic
is performed by each thread of the parallel section while the single
section is executed by only one.