如何正确移动/从shared_ptr读取

问题描述:

我的程序中有一个缓存系统。我有一个单独的静态类来维护这个缓存,并且同时在多个线程中使用缓存。我遇到了正确维护缓存系统的问题。这里是一些示例代码。如何正确移动/从shared_ptr读取

class db_cache 
{ 
    public: 
     typdef std::map<int, int> map_t; 

     static void update_cache(); 
     static boost::shared_ptr< const map_t > get_cache(); 

    private: 
     db_cache(); 
     static void run_update(); 
     static boost::shared_ptr< const map_t > cur_cache_; 
     static boost::shared_ptr< const map_t > old_cache_; 
}; 

void db_cache::update_cache() 
{ 
    cur_cache_ = boost::make_shared<map_t>(); 
    old_cache_ = boost::make_sahred<map_t>(); 

    // 
    //Setup connection to server that sends updates 
    // 

    //Initialize cache 
    run_update(); 
    while(true) 
    { 
     if(recv().compare("update") == 0) 
     { 
       //Update cache if update message recieved 
       run_update(); 
     } 
    } 
} 

void db_cache::run_update() 
{ 
    //Create new cache to swap with current cache 
    auto new_cache = boost:make_shared<map_t>(); 

    // 
    //Put data in new cache 
    // 

    boost::atomic_store(&old_cache_, boost::move(cur_cache_)); 
    boost::atomic_store(&cur_cache_, boost::shared_ptr< const map_t >(boost::move(new_cache))); 
} 

auto db_cache::get_cache() -> boost::shared_ptr< const map_t > 
{ 
    return boost::atomic_load(&cur_cache_); 
} 

我目前在boost::atomic_store(&old_cache_, boost::move(cur_cache_));发生崩溃。崩溃似乎是因为old_cache_为空。这似乎在第二次收到更新消息时发生。我假设发生了什么(不是100%肯定,但我能想到的只有一条路),是:

  1. 第一时间收到消息,cur_cache_被复制到old_cache_
  2. cur_cache_被替换为new_cache,导致旧的cur_cache_old_cache_当前也指向的)为空。
  3. old_cache_boost::atomic_store由于为空而再次被调用时会导致崩溃。

我的问题是,为什么boost::atomic_store(&old_cache_, boost::move(cur_cache_));不会导致参考计数器cur_cache_增加。我能做到这一点吗?

其他说明:

我之所以old_cache_是因为我相信从缓存中,这是最有可能也是一个问题读书时,我有一个问题。当我尝试从get_cache()返回的地图中读取元素时,我的程序似乎崩溃了,所以为了确保它们保持在范围内,直到所有当前有副本的线程都完成为止,我保存最后一个版本的缓存。我想如果我有正确的方法来做到这一点,我可以摆脱old_cache_一起,这应该解决上述问题。

编辑:

下面是使用缓存的代码:

//Vector big, don't want to copy 
const std::vector *selected_vec = &(*db_cache::get_cache()).at(get_string); 
for(std::vector<std::string>::iterator it = selected_vec->begin(), e = selected_vec->end(); it != e; ++it) 
{ 
    //String can be big, don't want to copy 
    const std::string *cur_string = &(*it); 
    if(cur_string == nullptr || cur_string->size() == 0) 
    { 
     continue; 
    } 
    char a = cur_string->at(0); //Crash here 

    //Do Stuff 
} 

我的实际map_t类型是std::map<std::string,std::vector<std::string>>类型不是std::map<int,int>。在调用get_cache()后,我得到我想要的矢量,并在矢量上进行迭代。对于每个字符串,我尝试获取第一个字符。当我尝试获取字符时,程序崩溃。我唯一能想到的就是selected_vec已被删除。

您在run_update有一场数据竞赛。其设定old_cache_行:

boost::atomic_store(&old_cache_, boost::move(cur_cache_)); 

正在执行非原子修改cur_cache_。回想的atomic_store签名:

namespace boost { 
template<class T> 
void atomic_store(shared_ptr<T>* p, shared_ptr<T> r); 
} 

顺带表达boost::move(cur_cache_)到第二个参数,你的函数由cur_cache_移动和离开它设置为nullptr创建实际参数的对象。即使此修改原子,此行与设置cur_cache_的较后行之间存在一个窗口,其中客户将看到从get_cache返回的nullptr。如果你绝对要保持在old_cache_cur_cache_的价值,你需要使用atomic_exchange同时设置cur_cache_和检索旧值:

void db_cache::run_update() 
{ 
    auto new_cache = boost:make_shared<map_t>(); 

    // ... 

    auto old = boost::atomic_exchange(&cur_cache_, boost::move(new_cache)); 
    boost::atomic_store(&old_cache_, boost::move(old)); 
} 

,但它会出现,你有没有用old_cache_一次比赛是固定的,你可以完全消除:

void db_cache::run_update() 
{ 
    auto new_cache = boost:make_shared<map_t>(); 

    // ... 

    boost::atomic_store(
     &cur_cache_, 
     boost::shared_ptr<const map_t>(boost::move(new_cache)) 
    ); 
} 

你原来问题的根源是在这个“客户”代码:

const std::vector *selected_vec = &(*db_cache::get_cache()).at(get_string); 

将指针存储到通过shared_ptr访问的对象的内部,但不保留对该对象shared_ptr所表示的对象的引用。当您稍后取消引用循环中的指针时,其指示对象可能已被销毁。你需要保持shared_ptr周围的副本,以确保当你使用它(和你不妨使用引用代替指针)指涉保持活着:

boost::shared_ptr<const map_t> cache = db_cache::get_cache(); 
//Vector big, don't want to copy 
const std::vector &selected_vec = cache->at(get_string); 
for(std::vector<std::string>::iterator it = selected_vec->begin(), e = selected_vec->end(); it != e; ++it) 
{ 
    //String can be big, don't want to copy 
    const std::string &cur_string = *it; 
    if(cur_string.size() == 0) 
    { 
     continue; 
    } 
    char a = cur_string.at(0); //Don't crash here 

    //Do Stuff 
} 
+0

大,谢谢你的'atomic_exchange'功能。至于第二部分,那原本就是我的,但我遇到了一个问题。我将用我的程序正在做的一些示例代码更新原始帖子。我想我的问题是,是否有任何理由可以想到,在使用'cur_cache'的线程完成运行之前,缓存将被删除。每次调用get_cache()时,我的'get_cache()'函数是否应该不增加'shared_ptr'的use_count? – Eumcoz 2015-02-24 14:14:29

+1

@Eumcoz'get_cache'返回'shared_ptr'的一个副本,它*增加了'use_count'。客户端代码中的问题是,它会删除'shared_ptr'的副本 - 在访问'shared_ptr'指向的对象的内部之前,减少'use_count' - * *。 – Casey 2015-02-24 16:54:47

+0

真棒,谢谢你的帮助,很有道理! – Eumcoz 2015-02-24 21:52:06

我想我可以为我的崩溃问题提供一个答案,但我仍然认为有更好的方法来设计解决方案。基本上,我的三个步骤列表是有人正确的,boost::atomic_store(&old_cache_, boost::move(cur_cache_));导致cur_cache_有0个引用,并且对象cur_cache_指向也被释放。下一次通过该函数,old_cache_然后指向释放的指针,并导致崩溃。我的解决办法是改变

boost::atomic_store(&old_cache_, boost::move(cur_cache_));

boost::atomic_store(&old_cache_, cur_cache_);

其停止cur_cache_的use_count增加至两个,第一个电话后,第二个电话去后回落到1。

我仍然认为有一个更好的方式来构建我的代码,而不必保留old_cache_,并会很乐意接受某人可以解释的答案。