5.4. Task Detachment#

The detach clause on a task construct provides a mechanism for an asynchronous routine to be called within a task block, and for the routine’s callback to signal completion to the OpenMP runtime, through an event fulfillment, triggered by a call to the omp_fulfill_event routine. When a detach clause is used on a task construct, completion of the detachable task occurs when the task’s structured block is completed AND an allow-completion event is fulfilled by a call to the omp_fulfill_event routine with the event-handle argument.

The first example illustrates the basic components used in a detachable task. The second example is a program that executes asynchronous IO, and illustrates methods that are also inherent in asynchronous messaging within MPI and asynchronous commands in streams within GPU codes. Interfaces to asynchronous operations found in IO, MPI and GPU parallel computing platforms and their programming models are not standardized.


The first example creates a detachable task that executes the asynchronous async_work routine, passing the omp_fulfill_event function and the (firstprivate) event handle to the function. Here, the omp_fulfill_event function is the “callback’’ function to be executed at the end of the async_work function’s asynchronous operations, with the associated data, event .

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

/*
* name:       task_detach.1
* type:       C
* version:    omp_5.0
*/
#include <omp.h>

void async_work(void (*)(void*), void*);
void work();

int main() {
  int async=1;
  #pragma omp parallel
  #pragma omp masked
  {

    omp_event_handle_t event;
    #pragma omp task detach(event)
    {
      if(async) {
        async_work( (void (*)(void*)) omp_fulfill_event, (void*) event );
      } else {
        work();
        omp_fulfill_event(event);
      }
    }
                  // Other work
    #pragma omp taskwait
  }
  return 0;
}
!!%compiler: gfortran
!!%cflags: -fopenmp

! name: task_detach.1
! type: F-free
! version:    omp_5.0
program main
  use omp_lib
  implicit none

  external :: async_work, work

  logical :: async=.true.
  integer(omp_event_handle_kind) :: event

  !$omp parallel
  !$omp masked

    !$omp task detach(event)

      if(async) then
        call async_work(omp_fulfill_event, event)
      else
        call work()
        call omp_fulfill_event(event)
      endif

    !$omp end task
                  !! Other work

    !$omp taskwait

  !$omp end masked
  !$omp end parallel

end program

In the following example, text data is written asynchronously to the file async_data , using POSIX asynchronous IO (aio). An aio “control block’’, cb , is set up to send a signal when IO is complete, and the sigaction function registers the signal action, a callback to callback_aioSigHandler .

The first task (TASK1) starts the asynchronous IO and runs as a detachable task. The second and third tasks (TASK2 and TASK3) perform synchronous IO to stdout with print statements. The difference between the two types of tasks is that the thread for TASK1 is freed for other execution within the parallel region, while the threads for TASK2 and TASK3 wait on the (synchronous) IO to complete, and cannot perform other work while the operating system is performing the synchronous IO. The if clause ensures that the detachable task is launched and the call to the aio_write function returns before TASK2 and TASK3 are generated (while the async IO occurs in the “background’’ and eventually executes the callback function). The barrier at the end of the parallel region ensures that the detachable task has completed.

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

/*
* name:       task_detach.2
* type:       C
* version:    omp_5.0
*/

// use -lrt on loader line
#include  <stdio.h>
#include <unistd.h>
#include  <fcntl.h>
#include    <aio.h>
#include  <errno.h>
#include <signal.h>

#include    <omp.h>

#define IO_SIGNAL SIGUSR1        // Signal used to notify I/O completion

                                 // Handler for I/O completion signal
static void callback_aioSigHandler(int sig, siginfo_t *si,
                                   void *ucontext) {
   if (si->si_code == SI_ASYNCIO){
      printf( "OUT: I/O completion signal received.\n");
      omp_fulfill_event( (omp_event_handle_t)(si->si_value.sival_ptr) );
   }
}

void work(int i){ printf("OUT: Executing work(%d)\n", i);}

int main() {
   // Write "Written Asynchronously." to file data, using POSIX
   // asynchronous IO. Error checking not included for clarity
   // and simplicity.

   char      data[] = "Written Asynchronously.";

   struct     aiocb cb;
   struct sigaction sa;

   omp_event_handle_t event;

   int fd = open(  "async_data", O_CREAT|O_RDWR|O_TRUNC,0664);

   // Setup async io (aio) control block (cb)
   cb.aio_nbytes  = sizeof(data)-1;
   cb.aio_fildes  = fd;
   cb.aio_buf     = data;
   cb.aio_reqprio = 0;
   cb.aio_offset  = 0;
   cb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
   cb.aio_sigevent.sigev_signo  = IO_SIGNAL;

   // Setup Signal Handler  Callback
   sigemptyset(&sa.sa_mask);
   sa.sa_flags = SA_RESTART | SA_SIGINFO;
   sa.sa_sigaction = callback_aioSigHandler;   //callback
   sigaction(IO_SIGNAL, &sa, NULL);

   #pragma omp parallel num_threads(2)
   #pragma omp masked
   {

      #pragma omp task detach(event) if(0)               // TASK1
      {
         cb.aio_sigevent.sigev_value.sival_ptr = (void *) event;
         aio_write(&cb);
      }

      #pragma omp task                                   // TASK2
         work(1);
      #pragma omp task                                   // TASK3
         work(2);

   } // Parallel region barrier ensures completion of detachable task.

   // Making sure the aio operation completed.
   // With OpenMP detachable task the condition will always be false:
   while(aio_error(&cb) == EINPROGRESS) {
   printf(" INPROGRESS\n");} //Safeguard

   close(fd);
   return 0;
}
/* Any Order:
OUT: I/O completion signal received.
OUT: Executing work(1)
OUT: Executing work(2)
*/