While I understand that QT states that only specifically stated classes are thread-safe, I'd like to understand why a "const" marked method - QPainterPath::contains() - breaks when it is being called in a parallel loop without any concurrent write operation:
#include <QPainterPath>
#include <omp.h>
#include <iostream>
int main(int argc, char *argv[])
{
QPainterPath path;
path.addRect(-50,-50,100,100);
#pragma omp parallel for
for(int x=0; x<100000; x)
if(!path.contains(QPoint(0,0)))
std::cout << "failed\n";
return 0;
}
The above code randomly outputs "failed", when it shouldn't.
My understanding is that it is changing its internal state somehow, despite the method being "const": https://code.woboq.org/qt5/qtbase/src/gui/painting/qpainterpath.cpp.html#_ZNK12QPainterPath8containsERK7QPointF
I need to compare if points are inside the path from multiple threads (in order to speed up processing), but it just does not work with QPainterPath. Even if I create a copy of the object for each thread, QT does Copy On Write and unless I change the derived object (to force it to detach), the result is still the same misbehaviour since it is still using the same shared data. How can I do that in a safe manner without this ugly hack?
CodePudding user response:
The answer is in the first line of code you linked to:
if (isEmpty() || !controlPointRect().contains(pt))
controlPointRect()
has the following:
if (d->dirtyControlBounds)
computeControlPointRect();
and computeControlPointRect()
does the following:
d->dirtyControlBounds = false;
...
d->controlBounds = QRectF(minx, miny, maxx - minx, maxy - miny);
In other words, if you call controlPointRect()
in parallel, the following can occur:
- Thread T1 sees
d->dirtyControlBounds
and enterscomputeControlPointRect()
which clears it. It gets to work computing the bounds. - Thread T2 enters
controlPointRect()
and sees thatd->dirtyControlBounds
is false. It checks whetherd->controlBounds
(at this point an empty set of points) contains that specific point. It does not, so it returns false. - Thread T1 finishes and updates
d->controlBounds
. All threads from now on are in sync.
The obvious fix for this specific instance is to make sure all dirty bits are cleared before you enter a massively parallel computation, but that might not be possible with all objects.