A variable subject to a reduction operation is not properly handled in a multithreading environment. This introduces a race condition making the result of the code unpredictable.
Protect the reduction operation.
A reduction operation is a typical computation pattern where multiple computations are merged into a single value using a mathematical operation. When those computations are executed in parallel, a race condition exists. To prevent this, the variable where the reduction is performed must be protected.
In the following code, the variable sum is subjected to a shared data
scoping. This introduces a race condition on the variable, since its read/write
operations are not properly protected:
void foo() {
int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sum = 0;
#pragma omp parallel for default(none) shared(array, sum)
for (int i = 0; i < 10; i++) {
sum += array[i];
}
}To protect the reduction operation, we can add an atomic directive from
OpenMP, which ensures that only one thread performs the read/write operation at
a given time:
void foo() {
int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sum = 0;
#pragma omp parallel for default(none) shared(array, sum)
for (int i = 0; i < 10; i++) {
#pragma omp atomic update
sum += array[i];
}
}Alternatively, we can also use the scalar reduction directive from OpenMP.
This will automatically create a copy of the variable for each thread, ensuring
they can perform their computations safely. Once all threads have finished,
their results are combined into the original variable using the specified
reduction operator:
void foo() {
int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sum = 0;
#pragma omp parallel for default(none) shared(array) reduction(+: sum)
for (int i = 0; i < 10; i++) {
sum += array[i];
}
}In the following code, the variable sum is subjected to a shared data
scoping. This introduces a race condition on the variable, since its read/write
operations are not properly protected:
subroutine example(array)
integer, intent(in) :: array(:)
integer :: i, sum
sum = 0
!$omp parallel do default(none) private(i) shared(array, sum)
do i = 1, size(array, 1)
sum = sum + array(i)
end do
end subroutine exampleTo protect the reduction operation, we can add an atomic directive from
OpenMP, which ensures that only one thread performs the read/write operation at
a given time:
subroutine example(array)
implicit none
integer, intent(in) :: array(:)
integer :: i, sum
sum = 0
!$omp parallel do default(none) private(i) shared(array, sum)
do i = 1, size(array, 1)
!$omp atomic update
sum = sum + array(i)
end do
end subroutine exampleAlternatively, we can also use the scalar reduction directive from OpenMP.
This will automatically create a copy of the variable for each thread, ensuring
they can perform their computations safely. Once all threads have finished,
their results are reduced into the original variable using the specified
reduction operator:
subroutine example(array)
implicit none
integer, intent(in) :: array(:)
integer :: i, sum
sum = 0
!$omp parallel do default(none) private(i) shared(array) reduction(+: sum)
do i = 1, size(array, 1)
sum = sum + array(i)
end do
end subroutine example-
OpenMP 4.5 Complete Specifications, November 2015 [last checked May 2019]