Skip to main content
GitHub

CON02-F. Protect reduction variables in multithreaded code

Programmers should protect shared variables used in reduction operations using synchronization techniques or parallel construct clauses to prevent data races.

A reduction combines multiple values into a single result using a mathematical operation (e.g., addition, multiplication, logical AND/OR). When executed in parallel, a reduction on a shared variable is typically implemented as a compound operation: read the current value, modify it, and write it back. Without proper protection, multiple threads can simultaneously read and update the variable, causing lost updates and undefined behavior.

In Fortran, reductions can be safely handled by OpenMP directives (such as reduction clause or atomic updates) or Fortran standard features like reduce locality specifier in do concurrent constructs or atomic subroutines.

Noncompliant Code Example

In this noncompliant example, the variable total_sum is shared among multiple threads. Because there is no protection for the addition operation, the read-modify-write sequence is non-atomic, leading to a data race and unpredictable final result.

Non-compliant code
subroutine calculate_sum(array)
  implicit none
  integer, intent(in) :: array(:)
  integer             :: i, total_sum

  total_sum = 0
  ! Noncompliant: total_sum is shared without protection
  !$omp parallel do default(none) private(i) shared(array, total_sum)
  do i = 1, size(array)
    total_sum = total_sum + array(i)
  end do
  !$omp end parallel do

  print *, "Sum is: ", total_sum
end subroutine

Compliant Solution

The most efficient way to handle this in OpenMP is to use the reduction clause. This automatically creates a private copy of the variable for each thread and combines the results once all threads have finished.

Compliant code
subroutine calculate_sum(array)
  implicit none
  integer, intent(in) :: array(:)
  integer             :: i, total_sum

  total_sum = 0
  ! Compliant: Using the reduction clause for safe parallelization
  !$omp parallel do default(none) private(i) shared(array) reduction(+: total_sum)
  do i = 1, size(array)
    total_sum = total_sum + array(i)
  end do
  !$omp end parallel do

  print *, "Sum is: ", total_sum
end subroutine

Compliant Solution

Alternatively, the atomic update directive can be used to ensure that only one thread performs the specific addition at a time.

Compliant code
! Compliant: Using atomic update to protect the shared variable
!$omp parallel do default(none) private(i) shared(array, total_sum)
do i = 1, size(array)
  !$omp atomic update
  total_sum = total_sum + array(i)
end do
!$omp end parallel do

Risk Assessment

Failing to protect reduction variables leads to data integrity violations where results are inconsistent and depend on thread timing. This can lead to incorrect state management or information disclosure.

RecommendationSeverityLikelihoodDetectableRepairablePriorityLevel
CON02-FHighLikelyYesYesP27L1

Bibliography

[ Fortran 2023 Interpretation Document ]Section 11.1.7.5 and Table 11.1 and 16.5

Attachments: