OpenMP task — common pitfall Link to heading
In the previous story, we explored how OpenMP’s task construct differs from section construct. In this story, I want to delve into the…
OpenMP — task common pitfall Link to heading
In the previous story, we explored how OpenMP’s task construct differs from section construct. In this story, I want to delve into the most common mistake with task construct that anyone can stumble upon.
Consider this simple example using OpenMP’s task construct:
// print_digits.cc
#include <iostream>
#include <omp.h>
int main(int argc, const char **argv)
{
int digit;
#pragma omp parallel
#pragma omp single
{
for (digit = 9; digit >= 0; --digit)
#pragma omp task
std::cout << digit;
}
std::cout << "\n";
return 0;
}
In this example, one thread decrements the digit variable from 9 to 0, and different digits may be printed by a different thread. What do you expect the output to be? Perhaps any permutation of 9876543210?
The actual output might surprise you:
$ clang++ -fopenmp print_digits.cc
$ ./a.out
2-18-122-1366
$ ./a.out
-1-1-17-1-10-10-1
What went wrong? When using the task construct, the default behavior is that the compiler may decide to defer the evaluation of the task expressions. In this example, the digit variable is shared across all threads, and there is a race condition between the for-loop and the std::cout statement.
When the task is deferred, all variables, including function parameters, are evaluated at a later time.
By the time the std::cout statement is executed, the for-loop has already ended, and the digit variable has a value of -1.
This is a common mistake that many developers encounter when using the task construct for the first time. Now that we understand what the problem is, let’s look at a simple fix:
--- before fix
+++ after fix
@@ -8,7 +8,7 @@
#pragma omp single
{
for (digit = 9; digit >= 0; --digit)
-#pragma omp task
+#pragma omp task firstprivate(digit)
std::cout << digit;
}
The fix is quite simple. We must make a copy of the value digit so that the task can execute the function with the correct value. Here, firstprivate will do just that — copy shared digit into private digit for the thread that will execute the task.
We can verify that the fix works as expected.
$ clang++ -fopenmp print_digits_fix.cc
$ ./a.out
9713204586
$ ./a.out
9786453120
*When using OpenMP’s
taskconstruct, always remember to usefirstprivateclause to safeguard from race-conditions