c++qtopenmpqpainterpath

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


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?


Solution

  • 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:

    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.