Home > OS >  Why QPainterPath::contains() is not thread-safe, despite being const?
Why QPainterPath::contains() is not thread-safe, despite being const?

Time:12-10

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 enters computeControlPointRect() which clears it. It gets to work computing the bounds.
  • Thread T2 enters controlPointRect() and sees that d->dirtyControlBounds is false. It checks whether d->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.

  • Related