在程序结束之前不执行所有写入

Not executing all writes before end of program

对于一个学校项目,我们的任务是编写光线追踪器。我选择使用 C++,因为它是我最熟悉的语言,但我遇到了一些奇怪的问题。

请记住,我们仍处于 class 的前几课,所以现在我们仅限于检查光线是否击中了某个物体。

当我的光线追踪器快速完成时(在实际光线追踪上花费的时间不到 1 秒),我注意到并非所有命中都在我的 "framebuffer".

中注册

为了说明,这里有两个例子:

1.: 2.:

在第一张图片中,您可以清楚地看到存在水平伪影。 第二张图片包含垂直伪影。

我想知道是否有人可以帮我弄清楚为什么会这样?

我应该提到我的应用程序是多线程的,代码的多线程部分如下所示:

Stats RayTracer::runParallel(const std::vector<Math::ivec2>& pixelList, const Math::vec3& eyePos, const Math::vec3& screenCenter, long numThreads) noexcept
{
    //...

    for (int i = 0; i < threads.size(); i++)
    {
        threads[i] = std::thread(&RayTracer::run, this, splitPixels[i], eyePos, screenCenter);
    }

    for (std::thread& thread: threads)
    {
        thread.join();
    }

    //...
}

RayTracer::run 方法访问帧缓冲区如下:

Stats RayTracer::run(const std::vector<Math::ivec2>& pixelList, const Math::vec3& eyePos, const Math::vec3& screenCenter) noexcept
{
    this->frameBuffer.clear(RayTracer::CLEAR_COLOUR);

    // ...

    for (const Math::ivec2& pixel : pixelList)
    {
        // ...

        for (const std::shared_ptr<Objects::Object>& object : this->objects)
        {
            std::optional<Objects::Hit> hit = object->hit(ray, pixelPos);

            if (hit)
            {
                // ...

                if (dist < minDist)
                {
                    std::lock_guard lock (this->frameBufferMutex);

                    // ...
                    this->frameBuffer(pixel.y, pixel.x) = hit->getColor();
                }
            }
        }
    }

    // ...
}

这是帧缓冲区的 operator() class

class FrameBuffer
{
    private:
        PixelBuffer buffer;

    public:
        // ...

        Color& FrameBuffer::operator()(int row, int col) noexcept
        {
            return this->buffer(row, col);
        }

        // ...
}

其中使用了 PixelBuffer 的 operator()

class PixelBuffer
{
    private:
        int mRows;
        int mCols;

        Color* mBuffer;

    public:
        // ...

        Color& PixelBuffer::operator()(int row, int col) noexcept
        {
            return this->mBuffer[this->flattenIndex(row, col)];
        }

        // ...
}

我懒得使用任何同步原语,因为每个线程都被分配了完整图像中的某个像素子集。该线程为其分配的每个像素投射一条光线,并将结果颜色写回该像素槽中的颜色缓冲区。这意味着,虽然我的所有线程都同时访问(和写入)同一个对象,但它们不会写入相同的内存位置。

经过一些初步测试,使用 std::lock_guard 保护共享帧缓冲区似乎有所帮助,但这不是一个完美的解决方案,仍然会出现瑕疵(尽管不太常见)。

应该注意的是,我在线程之间划分像素的方式决定了伪像的方向。如果我给每个线程一组行,伪像将是水平线,如果我给每个线程一组列,伪像将是垂直线。

另一个有趣的结论是,当我追踪更复杂的对象(这些对象需要 30 秒到 2 分钟之间的任何时间)时,这些人工制品极为罕见(到目前为止,我在 100 到 1000 条追踪中见过一次)

总觉得这是多线程相关的问题,但不明白为什么std::lock_guard不能完全解决问题

编辑:Jeremy Friesner 的建议下,我 运行 raytracer 在单个线程上运行了大约 10 次,没有任何问题,所以问题确实存在似乎是竞争条件。

感谢 Jeremy Friesner,我解决了这个问题。

正如您在代码中看到的,每个线程分别调用 framebuffer.clear()(没有锁定互斥量!)。这意味着线程 A 可能已经达到 5-10 个像素,因为它是在线程 B 清除帧缓冲区时首先启动的。这将擦除线程 A 已经命中的像素。

通过将 framebuffer.clear() 调用移动到 runParallel() 方法的开头,我能够解决问题。