1

虽然我知道 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 也会在写入时复制,除非我更改派生对象(强制它分离),否则结果仍然是相同的错误行为,因为它仍然使用相同的共享数据。如果没有这个丑陋的黑客,我怎样才能以安全的方式做到这一点?

4

1 回答 1

2

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

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(此时是一组空的点)是否包含该特定点。它没有,所以它返回false。
  • 线程 T1 完成并更新d->controlBounds。从现在开始,所有线程都是同步的。

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

于 2021-12-09T15:12:26.733 回答