我目前有一个程序具有类似缓存的机制。我有一个线程监听从另一台服务器到这个缓存的更新。该线程将在收到更新时更新缓存。这是一些伪代码:
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 应该自行删除。
关于我的程序的一点点:我的程序是一个网络服务器,它将使用这些信息来决定返回什么信息。这是相当高的吞吐量(数千个请求/秒)每个请求查询缓存一次,所以告诉我的其他线程何时更新是没有问题的。我可以容忍稍微过时的信息,如果可能的话,我更愿意阻止我所有的线程执行。缓存中的信息相当大,因此我想限制任何拷贝的值(value)。
update_cache
只运行一次。它在一个线程中运行,该线程仅监听更新命令并运行代码。
最佳答案
我觉得有很多问题:
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() 成为缓存公共(public)接口(interface)的一部分,因为它包含一个无限循环。我可能会用 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 的内容。
总的来说,如果您在锁定部分之外进行代价高昂的操作(例如内存分配),您可以保持较低的延迟(一切都是相对的,我不知道您的用例)。
关于c++ - 无阻塞更新缓存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22647222/