I want to avoid a race condition in parallel code. The issue is that my class contains several global variables, let's say just one x
for simplicity as well as a for
loop that I wish to make parallel. The actual code also has a method that takes a pointer to a class, in this case itself, as its argument, accessing even more global variables. So it might make sense to make the entire instance threadprivate.
I am using OpenMP.
A minimum working example is:
#include <iostream>
#include <omp.h>
class lotswork {
public:
int x;
int f[10];
lotswork(int i = 0) { x = i; };
void addInt(int y) { x = x + y; }
void carryout(){
#pragma omp parallel for
for (int n = 0; n < 10; ++n) {
this->addInt(n);
f[n] = x;
}
for(int j=0;j<10;++j){
std::cout << " array at " << j << " = " << f[j] << std::endl;
}
std::cout << "End result = " << x << std::endl;
}
};
int main() {
lotswork production(0);
#pragma omp threadprivate(production)
production.carryout();
}
My question is, how can I do this? Using the keyword threadprivate
returns the following compiler error message:
error: ‘production’ declared ‘threadprivate’ after first use
I think this compiler issue here still hasn't been solved:
This brings us to why I used the Intel compiler. Visual Studio 2013 as well as g++ (4.6.2 on my computer, Coliru (g++ v5.2), codingground (g++ v4.9.2)) allow only POD types (source). This is listed as a bug for almost a decade and still hasn't been fully addressed. The Visual Studio error given is error C3057: 'globalClass' : dynamic initialization of 'threadprivate' symbols is not currently supported and the error given by g++ is error: 'globalClass' declared 'threadprivate' after first use The Intel compiler works with classes.
Unfortunately, I haven't got access to Intel's compiler but use GCC 8.1.0. I did a bit of background research and found a discussion on this here, but that trail runs cold, ten years ago. I am asking this question because several people have had issues with this and solved it either by declaring a class pointer as here or proposing terrible workarounds. The latter approach seems misguided because a pointer is usually declared as a constant but then we have threadprivate
pointers while the instance is still shared.
I believe I can use the private
keyword but am unsure how to do this with an entire instance of a class although I'd prefer the threadprivate
keyword. A similar example to mine above on which I modeled my MWE has also been discussed in Chapter 7, Figure 7.17 in this book, but without solution. (I am well aware about the race condition and why it's a problem.)
If necessary I can give evidence that the output of the above programme without any extra keywords is nondeterministic.
I have now thought of a solution but for some reason, it won't compile. From a thread-safety and logical standpoint my problem should be solved by the following code. Yet, there must be some sort of error.
#include <iostream>
#include <omp.h>
class lotswork : public baseclass {
public:
int x;
int f[10];
lotswork(int i = 0) { x = i; };
void addInt(int y) { x = x + y; }
void carryout(){
//idea is to declare the instance private
#pragma omp parallel firstprivate(*this){
//here, another instance of the base class will be instantiated which is inside the parallel region and hence automatically private
baseclass<lotswork> solver;
#pragma omp for
for (int n = 0; n < 10; ++n)
{
this->addInt(n);
f[n] = x;
solver.minimize(*this,someothervariablethatisprivate);
}
} //closing the pragma omp parallel region
for(int j=0;j<10;++j){
std::cout << " array at " << j << " = " << f[j] << std::endl;
}
std::cout << "End result = " << x << std::endl;
}
};
int main() {
lotswork production(0);
#pragma omp threadprivate(production)
production.carryout();
}
So this code, based on the definitions, should do the trick but somehow it doesn't compile. How can I put this code together so it achieves the desired thread-safety and compiles, respecting the constraint that threadprivate is not an option for non-Intel compiler folks?
It seems like there is some confusion about OpenMP constructs here. threadprivate
is used, much like thread_local, to create a per-thread copy of an object of static lifetime, either a global or a static variable. As noted, there are some implementation issues with this, but even if the implementations could handle the class, using threadprivate
on a non-static local variable would produce an error.
As to the error, it's hard to say without output, but it is likely multiple things:
{
on the end of a pragma line does not open a block, it needs to be on the following line.If you need to create a private copy of the enclosing class in each thread, it's possible by either copy-constructing the class into a variable declared inside a parallel region:
#pragma omp parallel
{
lotswork tmp(*this);
// do things with private version
}
Note however that the entire thing is private, so this means that f
in the original copy will not be updated unless you perform the addInt
equivalents all on the private copies then the f[n]
assignments on the original.
Edit: I originally mentioned using the default(firstprivate)
clause, but the
default clause only offers private and first private for FORTRAN. To get the
same effect in c++, do the above and copy construct into a new instance of each,
or use a lambda with capture by value by default then firstprivate that, *this
requires c++17 to work, but does exactly what's requested:
auto fn = [=,*this](){
// do things with private copies
// all updates to persist in shared state through pointers
};
#pragma omp parallel firstprivate(fn)
fn();
This is a long-standing missing GCC feature:
With current GCC versions, thread_local
is expected to work, though:
int main() {
thread_local lotswork production(0);
production.carryout();
}
However, I do not think this will work in your case because the parallel loop in carryout
will still operate on a single lotswork
instance. I believe this would apply to the original code using threadprivate
, too. You probably need to move the parallel loop outside of the carryout
member function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With