在我的多线程图形应用程序中,我有一些资产,如图像,模型,声音文件等。其中一些是从磁盘上的文件加载的。当这些文件发生变化时,我想自动重新加载它们并更新可能在整个应用程序中使用的相应资产。类似的用例是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);
}
有更好的方法吗?我觉得我过于复杂,并监督了一个更简单的解决方案。这种情况如何得到解决?我的目标是为大型资产集合的迭代提供简单的界面和良好的性能。
答案 0 :(得分:1)
使用互斥锁几乎可以保证您在使用资产时会出现口吃。考虑您开始加载另一个版本的资产,然后显示例程想要使用它,但它被锁定,因此线程被阻塞,直到它被解锁。
您可以使用share_ptr,消费者将在资产上保留share_ptr,直到不再使用它为止。
加载器只加载新数据,加载完成后对资产进行原子开关,以便消费者的下一个请求获得新资产。
PS。 一些代码审查。
难以阅读的类型声明
的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*>
如果这就是你的真实含义。