NERSCPowering Scientific Discovery Since 1974

Introduction to OpenMP Tasks

Introduction to OpenMP Tasks

Tasks in OpenMP are code blocks that the compiler wraps up and makes available to be executed in parallel.

#pragma omp parallel
{
#pragma omp task
printf("hello world from a random thread\n");
}

Like worksharing constructs, tasks must also be created inside of a parallel region. In this example every thread makes a print world task. In order to only spawn a task once, the single construct is used.

#pragma omp parallel
{
#pragma omp single
{
#pragma omp task
printf("hello world\n");

#pragma omp task
printf("hello again!\n");
}}

This example will only print hello world once, but the ordering of hello world and hello again is undefined. The only guarantee about when either task will end is provided by the barrier at the end of the parallel region. There are two ways to specify the order: the taskwait directive, and task dependencies, which will be covered later.

#pragma omp parallel
{
#pragma omp single
{
#pragma omp task
printf("hello world\n");

#pragma omp taskwait

#pragma omp task
printf("hello again!\n");
}}

Data Sharing Attributes for Tasks

The tasks so far haven't involved any variables. Like other OpenMP constructs, variables that are used inside a task can be specified explicitly with clauses (shared, firstprivate, private, etc.) or implicitly, if not otherwise specified. In general, data accessed by a task is shared.

#pragma omp parallel
{
int x = 0;
#pragma omp single
{
#pragma omp task
{
x++;
printf("from task 1: x = %d\n", x);
}
#pragma omp taskwait

#pragma omp task
{
x++;
printf("from task 2: x = %d\n", x);
}
}}

In this example, x will be outputted as 1 and then 2, since both tasks are referencing the implicitly shared variable x.

#pragma omp parallel
{
int x = 0;
#pragma omp single
{
#pragma omp task firstprivate(x)
{
x++;
printf("from task 1: x = %d\n", x);
}
#pragma omp taskwait

#pragma omp task firstprivate(x)
{
x++;
printf("from task 2: x = %d\n", x);
}
}}

This example explicitly makes the x firstprivate, so both tasks increment their own copy of x, and both print x=1. Changing firstprivate to private in this case wouldn't be valid, since x is not initialized to anything inside of the task before being used.

Advanced Data Sharing with Tasks

The programmer must ensure, by adding proper synchronization, that storage shared by an explicit task region does not reach the end of its lifetime before the explicit task region completes its execution.

#pragma omp parallel
{
#pragma omp single
{
#pragma omp task
{
int x = 0;
#pragma omp task
{
x++;
printf("x = %d\n");
}
}
}}

This example shows one task spawning another task, with the inner task accessing a variable local to the outer task. There is a problem with this however, even with only one task modifying the variable; x exists on the stack of the outer task, and the outer task can finish before the inner task begins.

Sharing data on the stack like this is not monitored or prevented by the compiler or runtime, and is entirely up to the programmer to insert a taskwait where needed.