锁定多线程热交换

时间:2015-04-26 11:12:18

标签: c++ multithreading design-patterns locking

在我的多线程图形应用程序中,我有一些资产,如图像,模型,声音文件等。其中一些是从磁盘上的文件加载的。当这些文件发生变化时,我想自动重新加载它们并更新可能在整个应用程序中使用的相应资产。类似的用例是LOD。当模特远离相机时,我想用更便宜的不太详细的版本替换它们。

当资产被替换时,应用程序的其他部分在不同的线程中运行,并可能读取这些资产。所以我需要一些锁定。如何在替换资产的同时提供适当的锁定,同时使应用程序的其他部分尽可能轻松地使用它们?

例如,我可以为资产提供一个抽象基类。这可能包含共享的互斥锁。然后,会有一个loader类,它在内部存储资产并返回对它们的引用。

class Asset {
public:
    std::shared_mutex access_;
};

class Loader {
public:
    template <typename T>
    T &load(std::string filename);
private:
    std::map<std::pair<std::string, std::type_index>, void*> assets_;
};

template <typename T>
class AssetLoaderTraits;

然而,可能有很多资产,所以让我们尝试更少的互斥量。例如,加载程序可以保留锁定资产的列表。只有一个互斥锁可以访问该列表。而且,我们不再需要资产基类了。

class Loader {
public:
    template <typename T>
    T &load(std::string filename);
    void lock(std::string filename);
    bool try_lock(std::string filename, std::chronos::duration trying);
    void unlock(std::string filename);
private:
    std::map<std::pair<std::string, std::type_index>, void*> assets_;
    std::shared_mutex map_access_;
    std::map<std::string> locked_assets_;
};

template <typename T>
class AssetLoaderTraits;

但是,我仍然认为这不是最佳解决方案。如果有数千个资产,则通常会批量处理。因此,在资产向量上存在循环,并且在每次迭代中都需要锁定机制。对于锁定,我还需要记住我想要使用的所有资产的文件名。 (此外,装载机持有锁也感觉很奇怪。)

std::vector<std::pair<std::string, Image&>> images;
Image &image = loader.load<Image>("/path/to/image.png");
images.push_back(std::make_pair("/path/to/image.png", image));
// ...

for (auto &i : images) {
    std::string &filename = i.first;
    Image &image = i.second;
    loader.lock(filename);
    // ...
    loader.unlock(filename);        
}

有更好的方法吗?我觉得我过于复杂,并监督了一个更简单的解决方案。这种情况如何得到解决?我的目标是为大型资产集合的迭代提供简单的界面和良好的性能。

1 个答案:

答案 0 :(得分:1)

使用互斥锁几乎可以保证您在使用资产时会出现口吃。考虑您开始加载另一个版本的资产,然后显示例程想要使用它,但它被锁定,因此线程被阻塞,直到它被解锁。

您可以使用share_ptr,消费者将在资产上保留share_ptr,直到不再使用它为止。

  • 如果将指向对象中数据的指针指向渲染函数,请记住要保留它,否则渲染函数可能引用NULL。
  • 记得在渲染不再使用它之后不引用指针。

加载器只加载新数据,加载完成后对资产进行原子开关,以便消费者的下一个请求获得新资产。

  • 如果2个消费者获得同一资产的2个不同版本,那么这是一个巨大的问题吗?它应该在一次更新后解决,除非它的音乐或声音或其他持续时间。

PS。 一些代码审查。

  • 使用互斥锁时,请尝试使用lock_guard作为RAII模式。
  • 由于性能的原因,通常最好避免使用std :: map(和std :: list)并使用std :: unordered_map或std :: vector。
  • 难以阅读的类型声明

    的std ::地图&LT;的std ::对&LT; std :: string,std :: type_index&gt;,void *&gt;

如果你使用using,你可以写这样的东西

using AssetId = std::pair<std::string, std::type_index>;

std::map<AssertId, void*>

如果这就是你的真实含义。