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