更新缓存而不阻塞

时间:2014-03-25 22:00:33

标签: c++ multithreading c++11 shared-ptr

我目前有一个具有类似缓存机制的程序。我有一个线程侦听从另一个服务器到此缓存的更新。该线程将在收到更新时更新缓存。这是一些伪代码:

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只运行一次。它在一个只侦听更新命令并运行代码的线程中运行。

2 个答案:

答案 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_);
}