为什么 QPainterPath::contains() 不是线程安全的,尽管是常量?

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

虽然我知道 QT 声明仅明确说明 类 是线程安全的,但我想了解为什么标记为“const”的方法 - QPainterPath::contains() - 在它执行时中断正在并行循环中调用,没有任何并发​​写入操作:

#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;
}

上面的代码在不应该的情况下随机输出“失败”。

我的理解是它正在以某种方式改变其内部状态,尽管方法是“const”: https://code.woboq.org/qt5/qtbase/src/gui/painting/qpainterpath.cpp.html#_ZNK12QPainterPath8containsERK7QPointF

我需要比较点是否在多个线程的路径内(以加快处理速度),但它不适用于 QPainterPath。即使我为每个线程创建对象的副本,QT 也会在写时复制并且除非我更改派生对象(以强制它分离),结果仍然是相同的错误行为,因为它仍在使用相同的共享数据。如果没有这个丑陋的 hack,我怎么能以安全的方式做到这一点?

答案在您链接到的第一行代码中:

if (isEmpty() || !controlPointRect().contains(pt))

controlPointRect() 具有以下内容:

if (d->dirtyControlBounds)
    computeControlPointRect();

computeControlPointRect() 执行以下操作:

d->dirtyControlBounds = false;
...
d->controlBounds = QRectF(minx, miny, maxx - minx, maxy - miny);

换句话说,如果您并行调用 controlPointRect(),可能会发生以下情况:

  • 线程 T1 看到 d->dirtyControlBounds 并输入 computeControlPointRect() 将其清除。它开始计算边界。
  • 线程 T2 进入 controlPointRect() 并发现 d->dirtyControlBounds 为假。它检查 d->controlBounds(此时是一个空的点集)是否包含该特定点。它没有,所以它 returns false.
  • 线程 T1 完成并更新 d->controlBounds。从现在开始所有线程都是同步的。

针对此特定实例的明显修复是确保在进入大规模并行计算之前清除所有脏位,但这可能不适用于所有对象。