如何正确地从shared_ptr移动/读取

时间:2015-02-23 20:26:07

标签: c++ multithreading caching boost 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时会导致崩溃。
  4. 我的问题是,为什么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()之后,我得到了我想要的向量,并在向量上得到了迭代器。对于每个字符串,我尝试获取第一个字符。当我尝试获取char时,程序崩溃了。我唯一能想到的是selected_vec被删除了。

2 个答案:

答案 0 :(得分:2)

您在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_客户端将看到从nullptr返回的get_cache。如果您绝对想要在cur_cache_中保留old_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
}

答案 1 :(得分:0)

我想我可以为我的崩溃问题找到答案,尽管我仍然认为有更好的方法来设计解决方案。基本上,我的三步列表是正确的,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增加为2,并在第二次通话后返回到1。

我仍然相信有一种更好的方法来构建我的代码而不必保留old_cache_,并且很乐意接受能够解释这一点的人的答案。