atomic Construct
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