Cancellation Constructs
12.4. Cancellation Constructs#
The following example shows how the cancel directive can be used to terminate an OpenMP region. Although the cancel construct terminates the OpenMP worksharing region, programmers must still track the exception through the pointer ex and issue a cancellation for the parallel region if an exception has been raised. The primary thread checks the exception pointer to make sure that the exception is properly handled in the sequential part. If cancellation of the parallel region has been requested, some threads might have executed phase_1(). However, it is guaranteed that none of the threads executed phase_2().
//%compiler: clang
//%cflags: -fopenmp
/*
* name: cancellation.1
* type: C++
* version: omp_4.0
*/
#include <iostream>
#include <exception>
#include <cstddef>
#define N 10000
extern void causes_an_exception();
extern void phase_1();
extern void phase_2();
void example() {
std::exception *ex = NULL;
#pragma omp parallel shared(ex)
{
#pragma omp for
for (int i = 0; i < N; i++) {
// no 'if' that prevents compiler optimizations
try {
causes_an_exception();
}
catch (std::exception *e) {
// still must remember exception for later handling
#pragma omp atomic write
ex = e;
// cancel worksharing construct
#pragma omp cancel for
}
}
// if an exception has been raised, cancel parallel region
if (ex) {
#pragma omp cancel parallel
}
phase_1();
#pragma omp barrier
phase_2();
}
// continue here if an exception has been thrown in
// the worksharing loop
if (ex) {
// handle exception stored in ex
}
}
The following example illustrates the use of the cancel construct in error handling. If there is an error condition from the allocate statement, the cancellation is activated. The encountering thread sets the shared variable err and other threads of the binding thread set proceed to the end of the worksharing construct after the cancellation has been activated.
!!%compiler: gfortran
!!%cflags: -fopenmp
! name: cancellation.1
! type: F-free
! version: omp_4.0
subroutine example(n, dim)
integer, intent(in) :: n, dim(n)
integer :: i, s, err
real, allocatable :: B(:)
err = 0
!$omp parallel shared(err)
! ...
!$omp do private(s, B)
do i=1, n
!$omp cancellation point do
allocate(B(dim(i)), stat=s)
if (s .gt. 0) then
!$omp atomic write
err = s
!$omp cancel do
endif
! ...
! deallocate private array B
if (allocated(B)) then
deallocate(B)
endif
enddo
!$omp end parallel
end subroutine
The following example shows how to cancel a parallel search on a binary tree as soon as the search value has been detected. The code creates a task to descend into the child nodes of the current tree node. If the search value has been found, the code remembers the tree node with the found value through an atomic write to the result variable and then cancels execution of all search tasks. The function search_tree_parallel groups all search tasks into a single task group to control the effect of the cancel taskgroup directive. The level argument is used to create undeferred tasks after the first ten levels of the tree.
//%compiler: clang
//%cflags: -fopenmp
/*
* name: cancellation.2
* type: C
* version: omp_5.1
*/
#include <stddef.h>
typedef struct binary_tree_s {
int value;
struct binary_tree_s *left, *right;
} binary_tree_t;
binary_tree_t *search_tree(binary_tree_t *tree, int value, int level) {
binary_tree_t *found = NULL;
if (tree) {
if (tree->value == value) {
found = tree;
}
else {
#pragma omp task shared(found) if(level < 10)
{
binary_tree_t *found_left = NULL;
found_left = search_tree(tree->left, value, level + 1);
if (found_left) {
#pragma omp atomic write
found = found_left;
#pragma omp cancel taskgroup
}
}
#pragma omp task shared(found) if(level < 10)
{
binary_tree_t *found_right = NULL;
found_right = search_tree(tree->right, value, level + 1);
if (found_right) {
#pragma omp atomic write
found = found_right;
#pragma omp cancel taskgroup
}
}
#pragma omp taskwait
}
}
return found;
}
binary_tree_t *search_tree_parallel(binary_tree_t *tree, int value) {
binary_tree_t *found = NULL;
#pragma omp parallel shared(found, tree, value)
{
#pragma omp masked
{
#pragma omp taskgroup
{
found = search_tree(tree, value, 0);
}
}
}
return found;
}
The following is the equivalent parallel search example in Fortran.
!!%compiler: gfortran
!!%cflags: -fopenmp
! name: cancellation.2
! type: F-free
! version: omp_5.1
module parallel_search
type binary_tree
integer :: value
type(binary_tree), pointer :: right
type(binary_tree), pointer :: left
end type
contains
recursive subroutine search_tree(tree, value, level, found)
type(binary_tree), intent(in), pointer :: tree
integer, intent(in) :: value, level
type(binary_tree), pointer :: found
type(binary_tree), pointer :: found_left => NULL(), &
found_right => NULL()
if (associated(tree)) then
if (tree%value .eq. value) then
found => tree
else
!$omp task shared(found) if(level<10)
call search_tree(tree%left, value, level+1, found_left)
if (associated(found_left)) then
!$omp critical
found => found_left
!$omp end critical
!$omp cancel taskgroup
endif
!$omp end task
!$omp task shared(found) if(level<10)
call search_tree(tree%right, value, level+1, found_right)
if (associated(found_right)) then
!$omp critical
found => found_right
!$omp end critical
!$omp cancel taskgroup
endif
!$omp end task
!$omp taskwait
endif
endif
end subroutine
subroutine search_tree_parallel(tree, value, found)
type(binary_tree), intent(in), pointer :: tree
integer, intent(in) :: value
type(binary_tree), pointer :: found
found => NULL()
!$omp parallel shared(found, tree, value)
!$omp masked
!$omp taskgroup
call search_tree(tree, value, 0, found)
!$omp end taskgroup
!$omp end masked
!$omp end parallel
end subroutine
end module parallel_search