在程序结束之前不执行所有写入
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() 方法的开头,我能够解决问题。
对于一个学校项目,我们的任务是编写光线追踪器。我选择使用 C++,因为它是我最熟悉的语言,但我遇到了一些奇怪的问题。
请记住,我们仍处于 class 的前几课,所以现在我们仅限于检查光线是否击中了某个物体。
当我的光线追踪器快速完成时(在实际光线追踪上花费的时间不到 1 秒),我注意到并非所有命中都在我的 "framebuffer".
中注册为了说明,这里有两个例子:
1.:
在第一张图片中,您可以清楚地看到存在水平伪影。 第二张图片包含垂直伪影。
我想知道是否有人可以帮我弄清楚为什么会这样?
我应该提到我的应用程序是多线程的,代码的多线程部分如下所示:
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() 方法的开头,我能够解决问题。