9.4. atomic Construct#

The following example avoids race conditions (simultaneous updates of an element of x by multiple threads) by using the atomic construct .

The advantage of using the atomic construct in this example is that it allows updates of two different elements of x to occur in parallel. If a critical construct were used instead, then all updates to elements of x would be executed serially (though not in any guaranteed order).

Note that the atomic directive applies only to the statement immediately following it. As a result, elements of y are not updated atomically in this example.

//%compiler: clang
//%cflags: -fopenmp

/*
* name: atomic.1
* type: C
* version: omp_3.1
*/
float work1(int i)
{
  return 1.0 * i;
}

float work2(int i)
{
   return 2.0 * i;
}

void atomic_example(float *x, float *y, int *index, int n)
{
  int i;

  #pragma omp parallel for shared(x, y, index, n)
    for (i=0; i<n; i++) {
      #pragma omp atomic update
      x[index[i]] += work1(i);
      y[i] += work2(i);
     }
}

int main()
{
  float x[1000];
  float y[10000];
  int index[10000];
  int i;

  for (i = 0; i < 10000; i++) {
    index[i] = i % 1000;
    y[i]=0.0;
  }
  for (i = 0; i < 1000; i++)
    x[i] = 0.0;
  atomic_example(x, y, index, 10000);
  return 0;
}
!!%compiler: gfortran
!!%cflags: -fopenmp

! name: atomic.1
! type: F-fixed
! version: omp_3.1
      REAL FUNCTION WORK1(I)
        INTEGER I
        WORK1 = 1.0 * I
        RETURN
      END FUNCTION WORK1

      REAL FUNCTION WORK2(I)
        INTEGER I
        WORK2 = 2.0 * I
        RETURN
      END FUNCTION WORK2

      SUBROUTINE SUB(X, Y, INDEX, N)
        REAL X(*), Y(*)
        INTEGER INDEX(*), N

        INTEGER I

!$OMP   PARALLEL DO SHARED(X, Y, INDEX, N)
          DO I=1,N
!$OMP       ATOMIC UPDATE
              X(INDEX(I)) = X(INDEX(I)) + WORK1(I)
            Y(I) = Y(I) + WORK2(I)
          ENDDO

      END SUBROUTINE SUB

      PROGRAM ATOMIC_EXAMPLE
        REAL X(1000), Y(10000)
        INTEGER INDEX(10000)
        INTEGER I

        DO I=1,10000
          INDEX(I) = MOD(I, 1000) + 1
          Y(I) = 0.0
        ENDDO

        DO I = 1,1000
          X(I) = 0.0
        ENDDO

        CALL SUB(X, Y, INDEX, 10000)

      END PROGRAM ATOMIC_EXAMPLE

The following example illustrates the read and write clauses for the atomic directive. These clauses ensure that the given variable is read or written, respectively, as a whole. Otherwise, some other thread might read or write part of the variable while the current thread was reading or writing another part of the variable. Note that most hardware provides atomic reads and writes for some set of properly aligned variables of specific sizes, but not necessarily for all the variable types supported by the OpenMP API.

//%compiler: clang
//%cflags: -fopenmp

/*
* name: atomic.2
* type: C
* version: omp_3.1
*/
int atomic_read(const int *p)
{
    int value;
/* Guarantee that the entire value of *p is read atomically. No part of
 * *p can change during the read operation.
 */
#pragma omp atomic read
     value = *p;
     return value;
}

void atomic_write(int *p, int value)
{
/* Guarantee that value is stored atomically into *p. No part of *p can
change
 * until after the entire write operation is completed.
 */
#pragma omp atomic write
    *p = value;
}
!!%compiler: gfortran
!!%cflags: -fopenmp

! name: atomic.2
! type: F-fixed
! version: omp_3.1
       function atomic_read(p)
       integer :: atomic_read
       integer, intent(in) :: p
! Guarantee that the entire value of p is read atomically. No part of
! p can change during the read operation.

!$omp atomic read
       atomic_read = p
       return
       end function atomic_read

       subroutine atomic_write(p, value)
       integer, intent(out) :: p
       integer, intent(in) :: value
! Guarantee that value is stored atomically into p. No part of p can change
! until after the entire write operation is completed.
!$omp atomic write
       p = value
       end subroutine atomic_write

The following example illustrates the capture clause for the atomic directive. In this case the value of a variable is captured, and then the variable is incremented. These operations occur atomically. This example could be implemented using the fetch-and-add instruction available on many kinds of hardware. The example also shows a way to implement a spin lock using the capture and read clauses.

//%compiler: clang
//%cflags: -fopenmp

/*
* name: atomic.3
* type: C
* version: omp_3.1
*/
int fetch_and_add(int *p)
{
   /* Atomically read the value of *p and then increment it. The
      previous value is returned. This can be used to implement a
      simple lock as shown below.
    */
   int old;
#pragma omp atomic capture
   { old = *p; (*p)++; }
   return old;
}

/*
 * Use fetch_and_add to implement a lock
 */
struct locktype {
   int ticketnumber;
   int turn;
};
void do_locked_work(struct locktype *lock)
{
   int atomic_read(const int *p);
   void work();

   // Obtain the lock
   int myturn = fetch_and_add(&lock->ticketnumber);
   while (atomic_read(&lock->turn) != myturn)
      ;
   // Do some work. The flush is needed to ensure visibility of
   // variables not involved in atomic directives

   #pragma omp flush
   work();
   #pragma omp flush
   // Release the lock
   fetch_and_add(&lock->turn);
}
!!%compiler: gfortran
!!%cflags: -fopenmp

! name: atomic.3
! type: F-fixed
! version: omp_3.1
       function fetch_and_add(p)
       integer:: fetch_and_add
       integer, intent(inout) :: p

! Atomically read the value of p and then increment it. The previous value
! is returned. This can be used to implement a simple lock as shown below.
!$omp atomic capture
       fetch_and_add = p
       p = p + 1
!$omp end atomic
       end function fetch_and_add
       module m
       interface
         function fetch_and_add(p)
           integer :: fetch_and_add
           integer, intent(inout) :: p
         end function
         function atomic_read(p)
           integer :: atomic_read
           integer, intent(in) :: p
         end function
       end interface
       type locktype
          integer ticketnumber
          integer turn
       end type
       contains
       subroutine do_locked_work(lock)
       type(locktype), intent(inout) :: lock
       integer myturn
       integer junk
! obtain the lock
        myturn = fetch_and_add(lock%ticketnumber)
        do while (atomic_read(lock%turn) .ne. myturn)
          continue
        enddo
! Do some work. The flush is needed to ensure visibility of variables
! not involved in atomic directives
!$omp flush
       call work
!$omp flush
! Release the lock
       junk = fetch_and_add(lock%turn)
       end subroutine
       end module