B6 - synchronisation directives
int counter = 0;
#pragma omp parallel for
for (int i = 0; i < 1000; i++) {
counter++;
}
printf("Counter = %d\n", counter);
- there, two simultaneous threads can access the same memory location with at least one writing, so there is no synchronisation
- this is called a data race
- consequently, the output may be a lower number than the expected
1000
critical
int counter = 0;
#pragma omp parallel for
for (int i = 0; i < 1000; i++) {
#pragma omp critical
{
counter++;
}
}
printf("Counter = %d\n", counter);
- only one thread can execute the protected block at the time
- hence, the result is guaranteed to be
1000
- when a thread enters the critical section, the others must wait until it exits
- it can protect multiple statements together
atomic
int counter = 0;
#pragma omp parallel for
for (int i = 0; i < 1000; i++) {
#pragma omp atomic
counter++;
}
printf("Counter = %d\n", counter);
- it protects an individual supported memory operation, usually with less overhead than
critical
- cannot generally protect an arbitrary block with atomic
reduction clauses
- for parallel sums,
reduction is the best choice as the thread accumulates into a private copy and OpenMP combines the results at the end
- this requires less synchronisation, making it faster for large sums
int sum = 0;
#pragma omp parallel for reduction(+:sum)
for (int i = 0; i < 1000; i++) {
sum += i;
}
- reduction can be done with multiplication,
&&, || and other associative binary operators
- however, results of a sequence of floating point operations may differ slightly, depending on the order in which they are combined, due to roundoff errors
optimising critical section
#pragma omp parallel for
for (int i = 0; i < N; i++) {
#pragma omp critical
{
if (a[i] > a_max) {
a_max = a[i];
}
}
}
- every iteration must enter the critical section making it very inefficient
#pragma omp parallel for
for (int i = 0; i < N; i++) {
if (a[i] > a_max) {
#pragma omp critical
{
if (a[i] > a_max) {
a_max = a[i];
}
}
}
}
- the outer
if avoids entering the critical section unnecessarily
naming critical sections
#pragma omp parallel for
for (i = 0; i < N; i++) {
if (a[i] > a_max) {
#pragma omp critical (maxlock)
if (a[i] > a_max) a_max = a[i];
}
if (a[i] < a_min) {
#pragma omp critical (minlock)
if (a[i] < a_min) a_min = a[i];
}
}
- now the two critical directives act like separate 'locks'
- program threads need not wait while other critical variables are updated
locks
omp_lock_t writelock;
omp_init_lock(&writelock);
#pragma openmp parallel for
for (i = 0; i < x; i++) {
// ...
omp_set_lock(&writelock);
// ...
omp_unset_lock(&writelock);
// ...
}
omp_destroy_lock(&writelock);