Lock Routines
Contents
9.11. Lock Routines#
This section is about the use of lock routines for synchronization.
9.11.1. omp_init_lock Routine#
The following example demonstrates how to initialize an array of locks in a parallel region by using omp_init_lock.
//%compiler: clang
//%cflags: -fopenmp
/*
* name: init_lock.1
* type: C++
*/
#include <omp.h>
omp_lock_t *new_locks() {
int i;
omp_lock_t *lock = new omp_lock_t[1000];
#pragma omp parallel for private(i)
for (i=0; i<1000; i++)
{ omp_init_lock(&lock[i]); }
return lock;
}
!!%compiler: gfortran
!!%cflags: -fopenmp
! name: init_lock.1
! type: F-fixed
FUNCTION NEW_LOCKS()
USE OMP_LIB ! or INCLUDE "omp_lib.h"
INTEGER(OMP_LOCK_KIND), DIMENSION(1000) :: NEW_LOCKS
INTEGER I
!$OMP PARALLEL DO PRIVATE(I)
DO I=1,1000
CALL OMP_INIT_LOCK(NEW_LOCKS(I))
END DO
!$OMP END PARALLEL DO
END FUNCTION NEW_LOCKS
9.11.2. omp_init_lock_with_hint Routine#
The following example demonstrates how to initialize an array of locks in a parallel region by using omp_init_lock_with_hint. Note, hints are combined with an | or + operator in C/C++ and a + operator in Fortran.
//%compiler: clang
//%cflags: -fopenmp
/*
* name: init_lock_with_hint.1
* type: C++
* version: omp_5.0
*/
#include <omp.h>
omp_lock_t *new_locks()
{
int i;
omp_lock_t *lock = new omp_lock_t[1000];
#pragma omp parallel for private(i)
for (i=0; i<1000; i++)
{
omp_init_lock_with_hint(&lock[i],
static_cast<omp_lock_hint_t>(omp_sync_hint_contended |
omp_sync_hint_speculative));
}
return lock;
}
!!%compiler: gfortran
!!%cflags: -fopenmp
! name: init_lock_with_hint.1
! type: F-fixed
! version: omp_5.0
FUNCTION NEW_LOCKS()
USE OMP_LIB ! or INCLUDE "omp_lib.h"
INTEGER(OMP_LOCK_KIND), DIMENSION(1000) :: NEW_LOCKS
INTEGER I
!$OMP PARALLEL DO PRIVATE(I)
DO I=1,1000
CALL OMP_INIT_LOCK_WITH_HINT(NEW_LOCKS(I),
& OMP_SYNC_HINT_CONTENDED + OMP_SYNC_HINT_SPECULATIVE)
END DO
!$OMP END PARALLEL DO
END FUNCTION NEW_LOCKS
9.11.3. Ownership of Locks#
Ownership of locks has changed since OpenMP 2.5. In OpenMP 2.5, locks are owned by threads; so a lock released by the omp_unset_lock routine must be owned by the same thread executing the routine. Beginning with OpenMP 3.0, locks are owned by task regions; so a lock released by the omp_unset_lock routine in a task region must be owned by the same task region.
This change in ownership requires extra care when using locks. The following program is conforming in OpenMP 2.5 because the thread that releases the lock lck in the parallel region is the same thread that acquired the lock in the sequential part of the program (primary thread of parallel region and the initial thread are the same). However, it is not conforming beginning with OpenMP 3.0, because the task region that releases the lock lck is different from the task region that acquires the lock.
//%compiler: clang
//%cflags: -fopenmp
/*
* name: lock_owner.1
* type: C
* version: omp_5.1
*/
#include <stdlib.h>
#include <stdio.h>
#include <omp.h>
int main()
{
int x;
omp_lock_t lck;
omp_init_lock (&lck);
omp_set_lock (&lck);
x = 0;
#pragma omp parallel shared (x)
{
#pragma omp masked
{
x = x + 1;
omp_unset_lock (&lck);
}
/* Some more stuff. */
}
omp_destroy_lock (&lck);
return 0;
}
!!%compiler: gfortran
!!%cflags: -fopenmp
! name: lock_owner.1
! type: F-fixed
! version: omp_5.1
program lock
use omp_lib
integer :: x
integer (kind=omp_lock_kind) :: lck
call omp_init_lock (lck)
call omp_set_lock(lck)
x = 0
!$omp parallel shared (x)
!$omp masked
x = x + 1
call omp_unset_lock(lck)
!$omp end masked
! Some more stuff.
!$omp end parallel
call omp_destroy_lock(lck)
end
9.11.4. Simple Lock Routines#
In the following example, the lock routines cause the threads to be idle while waiting for entry to the first critical section, but to do other work while waiting for entry to the second. The omp_set_lock function blocks, but the omp_test_lock function does not, allowing the work in skip to be done.
Note that the argument to the lock routines should have type omp_lock_t (or omp_lock_kind in Fortran), and that there is no need to flush the lock variable ( lck ).
//%compiler: clang
//%cflags: -fopenmp
/*
* name: simple_lock.1
* type: C
*/
#include <stdio.h>
#include <omp.h>
void skip(int i) {}
void work(int i) {}
int main()
{
omp_lock_t lck;
int id;
omp_init_lock(&lck);
#pragma omp parallel shared(lck) private(id)
{
id = omp_get_thread_num();
omp_set_lock(&lck);
/* only one thread at a time can execute this printf */
printf("My thread id is %d.\n", id);
omp_unset_lock(&lck);
while (! omp_test_lock(&lck)) {
skip(id); /* we do not yet have the lock,
so we must do something else */
}
work(id); /* we now have the lock
and can do the work */
omp_unset_lock(&lck);
}
omp_destroy_lock(&lck);
return 0;
}
!!%compiler: gfortran
!!%cflags: -fopenmp
! name: simple_lock.1
! type: F-fixed
SUBROUTINE SKIP(ID)
END SUBROUTINE SKIP
SUBROUTINE WORK(ID)
END SUBROUTINE WORK
PROGRAM SIMPLELOCK
INCLUDE "omp_lib.h" ! or USE OMP_LIB
INTEGER(OMP_LOCK_KIND) LCK
INTEGER ID
CALL OMP_INIT_LOCK(LCK)
!$OMP PARALLEL SHARED(LCK) PRIVATE(ID)
ID = OMP_GET_THREAD_NUM()
CALL OMP_SET_LOCK(LCK)
PRINT *, 'My thread id is ', ID
CALL OMP_UNSET_LOCK(LCK)
DO WHILE (.NOT. OMP_TEST_LOCK(LCK))
CALL SKIP(ID) ! We do not yet have the lock
! so we must do something else
END DO
CALL WORK(ID) ! We now have the lock
! and can do the work
CALL OMP_UNSET_LOCK( LCK )
!$OMP END PARALLEL
CALL OMP_DESTROY_LOCK( LCK )
END PROGRAM SIMPLELOCK
9.11.5. Nestable Lock Routines#
The following example demonstrates how a nestable lock can be used to synchronize updates both to a whole structure and to one of its members.
//%compiler: clang
//%cflags: -fopenmp
/*
* name: nestable_lock.1
* type: C
*/
#include <omp.h>
typedef struct {
int a,b;
omp_nest_lock_t lck;
} pair;
int work1();
int work2();
int work3();
void incr_a(pair *p, int a)
{
/* Called only from incr_pair, no need to lock. */
p->a += a;
}
void incr_b(pair *p, int b)
{
/* Called both from incr_pair and elsewhere, */
/* so need a nestable lock. */
omp_set_nest_lock(&p->lck);
p->b += b;
omp_unset_nest_lock(&p->lck);
}
void incr_pair(pair *p, int a, int b)
{
omp_set_nest_lock(&p->lck);
incr_a(p, a);
incr_b(p, b);
omp_unset_nest_lock(&p->lck);
}
void nestlock(pair *p)
{
#pragma omp parallel sections
{
#pragma omp section
incr_pair(p, work1(), work2());
#pragma omp section
incr_b(p, work3());
}
}
!!%compiler: gfortran
!!%cflags: -fopenmp
! name: nestable_lock.1
! type: F-fixed
MODULE DATA
USE OMP_LIB, ONLY: OMP_NEST_LOCK_KIND
TYPE LOCKED_PAIR
INTEGER A
INTEGER B
INTEGER (OMP_NEST_LOCK_KIND) LCK
END TYPE
END MODULE DATA
SUBROUTINE INCR_A(P, A)
! called only from INCR_PAIR, no need to lock
USE DATA
TYPE(LOCKED_PAIR) :: P
INTEGER A
P%A = P%A + A
END SUBROUTINE INCR_A
SUBROUTINE INCR_B(P, B)
! called from both INCR_PAIR and elsewhere,
! so we need a nestable lock
USE OMP_LIB ! or INCLUDE "omp_lib.h"
USE DATA
TYPE(LOCKED_PAIR) :: P
INTEGER B
CALL OMP_SET_NEST_LOCK(P%LCK)
P%B = P%B + B
CALL OMP_UNSET_NEST_LOCK(P%LCK)
END SUBROUTINE INCR_B
SUBROUTINE INCR_PAIR(P, A, B)
USE OMP_LIB ! or INCLUDE "omp_lib.h"
USE DATA
TYPE(LOCKED_PAIR) :: P
INTEGER A
INTEGER B
CALL OMP_SET_NEST_LOCK(P%LCK)
CALL INCR_A(P, A)
CALL INCR_B(P, B)
CALL OMP_UNSET_NEST_LOCK(P%LCK)
END SUBROUTINE INCR_PAIR
SUBROUTINE NESTLOCK(P)
USE OMP_LIB ! or INCLUDE "omp_lib.h"
USE DATA
TYPE(LOCKED_PAIR) :: P
INTEGER WORK1, WORK2, WORK3
EXTERNAL WORK1, WORK2, WORK3
!$OMP PARALLEL SECTIONS
!$OMP SECTION
CALL INCR_PAIR(P, WORK1(), WORK2())
!$OMP SECTION
CALL INCR_B(P, WORK3())
!$OMP END PARALLEL SECTIONS
END SUBROUTINE NESTLOCK