【问题标题】:C++ multi-threaded texture loader work item removal from main thread从主线程中删除 C++ 多线程纹理加载器工作项
【发布时间】:2017-02-06 17:37:41
【问题描述】:

我有一个纹理加载线程,它通过并发队列从主线程接收加载纹理的请求。

纹理加载器请求是一个简单的结构,带有一个指向将接收纹理的对象的原始指针:

struct TextureLoaderRequest
{
    std::string mFilename;
    ContentViewer *mContentViewer;
};

ContentViewer 中包含的实际纹理对象受互斥体和一些原子布尔值(也包含在 ContentViewer 中)的保护:

std::atomic<bool>               mIsLoaded;
std::atomic<bool>               mIsVisible;
std::mutex                      mImageMutex;

那么纹理访问例程如下:

void ContentViewer::setTexture(ci::gl::TextureRef texture)
{
    std::lock_guard<std::mutex> guard(mImageMutex);
    mImage = texture;
}

ci::gl::TextureRef ContentViewer::getTexture()
{
    std::lock_guard<std::mutex> guard(mImageMutex);
    if (mIsVisible)
    {
        if (mImage != nullptr)
        {
            mIsLoaded = true;
            return mImage;
        }
        mIsLoaded = false;
    }
    return nullptr;
}

纹理加载器一次可能会从主线程接收多个纹理加载请求,然后通过队列加载并将纹理分配给纹理加载请求消息中指向的内容查看器。

我遇到的问题是,当主线程“删除”内容查看器时,纹理加载线程的队列中可能有一个未完成的请求,当它开始处理它时,内容查看器已被删除然后程序崩溃了。

我不确定如何删除纹理线程工作队列中的未完成的纹理加载请求。我不能让主线程等待为内容查看器加载相关纹理,但是,实现这一点的最佳实践策略是什么?

谢谢 - 铺设

【问题讨论】:

  • 使用 std::shared_ptr 会有帮助吗?
  • 我已将您的问题重新提交给 opengl-es。 (opengl 标签表示 desktop-gl)。如果您的意思是 desktop-gl,请随意标记它并删除 opengl-es 标记。
  • 你能分享你的纹理工作线程函数吗?
  • @xaxxon shared_ptr 在这里有什么用处?我猜纹理请求可能有一个weak_ptr,然后线程检查weak_ptr以确定有效性。我想我仍然需要围绕共享指针进行同步。对我来说,在这个用例中使用原始指针似乎更干净,但我不是使用共享指针的专家。
  • @farhat latrach 请见下文

标签: c++ multithreading opengl-es opengl-es-3.0


【解决方案1】:

好的,恕我直言,有两种选择:

  1. 您等待对特定对象的所有请求都完成,然后再删除它。
  2. 在执行任何计划的操作之前检查对象是否仍然存在。

我对您的应用程序没有足够的了解:队列是如何实现的以及为什么以及何时安排请求,因此我无法就此提供反馈。

【讨论】:

  • 嗨,我等不及要兑现请求了。这段代码是用来执行关闭操作的,因此预期的效果应该是取消未完成的请求。您提到的第二个选项是我的第一次尝试,我发现如果我检查有效性,然后使用该对象,它可能在检查和使用之间消失,因此崩溃。我确实找到了一个解决方案,我将在下面概述。感谢您的帮助!
  • 在多线程系统中,您必须锁定对象以防止出现这些情况,例如在检查有效性后删除对象。否则无法防止您描述的情况。因此,例如删除方法也必须锁定 mImageMutex。您的问题是多线程中的常见问题。就像这里所说的 [link]stackoverflow.com/questions/12455297/… :您不能删除正在使用的对象。没有多少互斥锁可以解决这个问题。
【解决方案2】:

我发现我需要从 std::mutex 受保护的向量中构造一个取消列表。当主线程想要退出时,它只需在向量中添加一个条目并继续。纹理加载线程有额外的负担检查每个接收到的纹理请求的列表,但操作不在关键路径上。

我仍然对替代方案/建议感兴趣。

线程的小轮廓如下:

void textureLoaderThreadFn()
{
    log("texture loader thread started");

    while (!mShouldQuit)
    {
        // Wait for texture loader request
        TextureLoaderRequest *textureLoaderRequest = nullptr;
        mTextureRequests->popBack(&textureLoaderRequest);

        // it is possible popBack didnt modify textureLoaderRequest (eg. when cancelled on exit)
        if (textureLoaderRequest != nullptr)
        {
            std::lock_guard<std::mutex> lk(mCancellationListMutex);

            if (std::find(mCancellationList.begin(), mCancellationList.end(), textureLoaderRequest->mFilename) != mCancellationList.end())
            {
                // Cancelled

                // we must reset the isLoading that was set by the main thread,
                // so that the request to load the texture can get put back if need be
                textureLoaderRequest->mContentViewer->mIsLoading = false;

                // remove from cancellation list
                mCancellationList.erase(std::remove(mCancellationList.begin(), mCancellationList.end(), textureLoaderRequest->mFilename), mCancellationList.end());
            }
            else
            {
                // Not cancelled
                <SERVICE TEXTURE REQUEST>
            }

            // dont need this anymore
            delete textureLoaderRequest;
        }
    }
    log("texture loader thread stopped");

    // Empty the queue    
    int count = 0;
    TextureLoaderRequest *textureLoaderRequest = nullptr;
    while (mTextureRequests->tryPopBack(&textureLoaderRequest))
    {
        if (textureLoaderRequest != nullptr)
            delete textureLoaderRequest;
        count++;
    }
    log("texture loader thread purged " + std::to_string(count) + " outstanding texture load requests");
}

【讨论】:

    【解决方案3】:

    我建议您在内容查看器中添加一个状态标志来区分 3 种状态;预定着色,被着色和未预定着色。 仅当内容查看器的状态为已安排着色或未安排着色时,主线程才应删除内容查看器。

    纹理工作线程将状态更改为上色,一旦上色,就将其设置为上色。

    状态的改变和状态是否可以删除的检查应该始终在同一个互斥锁范围内;您可以在内容查看器中将标志设为私有并使用两个公共方法 1)void change_status(status) 和 2)bool can_delete()。

    这两个函数都应该从获取相同的互斥锁开始。 1) 用于主线程和纹理工作线程中的不同转换,2) 由主线程在删除内容查看器之前使用,仅当状态未着色时才返回 true。

    在纹理工作线程中,在退出之前,您可以删除最后一个着色内容,以防主线程没有删除它(因为它可能处于着色状态)。

    【讨论】:

    • 主线程无法删除它,因为它的状态是“正在着色”; can_delete 将返回 false。然后这是您在退出纹理线程时删除的最后一个...
    • 您好,如果主线程想要在执行预定请求之前关闭内容查看器,到目前为止我想出的唯一方案是记下我们已删除的事实内容查看器(我在下面提到的互斥保护取消列表),然后让纹理加载器根据取消列表中的请求目标地址取消纹理请求。这是我在下面概述的选项,似乎有效,但尚未经过压力测试。我不确定为什么我们需要用 std:mutex 保护原子布尔标志?
    • 你的意思是从纹理加载线程本身中删除内容查看器吗?
    • 另外,“主线程应该删除一个内容查看器,只有当它的状态被安排为着色或没有被安排为着色” - 在这种情况下,主线程会遇到内容查看器吗被着色(加载)?谢谢
    • 跳过并且不删除它,因为它正在使用。始终应该只有一个处于此状态。我假设主线程在决定删除内容查看器时也会关闭纹理线程,因此纹理线程工作人员在退出时会删除它使用过的最后一个内容查看器(如果尚未删除);说就像你总是存储最后一个指针并在退出之前检查它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多