我目前有一个具有类似缓存机制的程序。我有一个线程侦听从另一个服务器到此缓存的更新。该线程将在收到更新时更新缓存。这是一些伪代码:
void cache::update_cache()
{
cache_ = new std::map<std::string, value>();
while(true)
{
if(recv().compare("update") == 0)
{
std::map<std::string, value> *new_info = new std::map<std::string, value>();
std::map<std::string, value> *tmp;
//Get new info, store in new_info
tmp = cache_;
cache_ = new_cache;
delete tmp;
}
}
}
std::map<std::string, value> *cache::get_cache()
{
return cache_;
}
正在同时从许多不同的线程中读取 cache_
。我相信我在这里有它如果我的一个线程调用get_cache()
,然后我的缓存更新,然后线程尝试访问存储的缓存,我将遇到未定义的行为。
我正在寻找一种避免这个问题的方法。我知道我可以使用互斥锁,但我宁愿不阻止读取发生,因为它们必须尽可能低延迟,但如果需要,我可以走这条路。
我想知道这对于unique_ptr是否是一个很好的用例。我的理解是正确的,如果一个线程调用get_cache,并返回一个unique_ptr而不是一个标准指针,一旦所有具有旧版本缓存的线程都完成了它(即保留范围),该对象将被删除。
对于这种情况,使用unique_ptr是最佳选择,还是我没有想到的其他选项?
非常感谢任何输入。
编辑:
我相信我的OP犯了一个错误。我的意思是使用并传递shared_ptr而不是cache_的unique_ptr。当所有线程都使用cache_完成时,shared_ptr应该自行删除。
关于我的程序:我的程序是一个网络服务器,它将使用这些信息来决定返回什么信息。它是相当高的吞吐量(数千req / sec)每个请求一次查询缓存,因此告诉我的其他线程何时更新没有问题。我可以容忍稍微过时的信息,并且如果可能的话,我希望阻止我的所有线程执行。缓存中的信息相当大,我想因此限制任何值的副本。
update_cache
只运行一次。它在一个只侦听更新命令并运行代码的线程中运行。
答案 0 :(得分:2)
我觉得有很多问题:
1)不要泄漏内存:因为从不在代码中使用“delete”并坚持使用unique_ptr(或在特定情况下使用shared_ptr)
2)保护对共享数据的访问,使用锁定(互斥)或无锁机制(std :: atomic)
class Cache {
using Map = std::map<std::string, value>();
std::unique_ptr<Map> m_cache;
std::mutex m_cacheLock;
public:
void update_cache()
{
while(true)
{
if(recv().compare("update") == 0)
{
std::unique_ptr<Map> new_info { new Map };
//Get new info, store in new_info
{
std::lock_guard<std::mutex> lock{m_cacheLock};
using std::swap;
swap(m_cache, new_cache);
}
}
}
}
注意:我不喜欢update_cache()作为缓存的公共接口的一部分,因为它包含无限循环。我可能会使用recv将循环外部化并具有:
void update_cache(std::unique_ptr<Map> new_info)
{
{ // This inner brace is not useless, we don't need to keep the lock during deletion
std::lock_guard<std::mutex> lock{m_cacheLock};
using std::swap;
swap(m_cache, new_cache);
}
}
现在读取缓存,使用正确的封装,不要将指针留给成员映射转义:
value get(const std::string &key)
{
// lock, fetch, and return.
// Depending on value type, you might want to allocate memory
// before locking
}
使用此签名,如果缓存中不存在该值,则必须抛出异常,另一个选项是返回类似boost :: optional的内容。
总的来说,如果你在锁定部分之外进行昂贵的操作(例如内存分配),你可以保持低延迟(一切都是相对的,我不知道你的用例)。
答案 1 :(得分:1)
shared_ptr
非常合理,C ++ 11有a family of functions for handling shared_ptr
atomically。如果数据在创建后是不可变的,那么您甚至不需要任何其他同步:
class cache {
public:
using map_t = std::map<std::string, value>;
void update_cache();
std::shared_ptr<const map_t> get_cache() const;
private:
std::shared_ptr<const map_t> cache_;
};
void cache::update_cache()
{
while(true)
{
if(recv() == "update")
{
auto new_info = std::make_shared<map_t>();
// Get new info, store in new_info
// Make immutable & publish
std::atomic_store(&cache_,
std::shared_ptr<const map_t>{std::move(new_info)});
}
}
}
auto cache::get_cache() const -> std::shared_ptr<const map_t> {
return std::atomic_load(&cache_);
}