【问题标题】:How to properly move/read from a shared_ptr如何正确移动/读取 shared_ptr
【发布时间】:2015-02-23 20:26:06
【问题描述】:

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

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(&amp;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(&amp;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&lt;std::string,std::vector&lt;std::string&gt;&gt; 类型而不是 std::map&lt;int,int&gt;。在调用get_cache() 之后,我得到了我想要的向量,以及向量上的迭代器。对于每个字符串,我尝试获取第一个字符。当我尝试获取字符时,程序崩溃了。我唯一能想到的就是selected_vec被删除了。

【问题讨论】:

    标签: c++ multithreading caching boost shared-ptr


    【解决方案1】:

    您在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_,客户端将在其中看到nullptrget_cache返回。如果您绝对想在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
    }
    

    【讨论】:

    • 太好了,感谢您提供atomic_exchange 功能。至于第二部分,本来是我有的,但我遇到了问题。我将使用我的程序正在执行的一些示例代码来更新原始帖子。我想我的问题是,你有什么理由认为在我使用cur_cache 的线程完成运行之前缓存将被删除。每次调用get_cache() 时,我的get_cache() 函数是否不应该增加shared_ptr 的use_count?
    • @Eumcoz get_cache 返回shared_ptr 的副本,确实增加了use_count。客户端代码中的问题是它丢弃了 shared_ptr 的副本 - 减少了 use_count - 它访问了 shared_ptr 指向的对象的内部。
    【解决方案2】:

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

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

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

    在第一次调用后停止 cur_cache_ 的 use_count 增加到 2,在第二次调用后又回到 1。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-07-29
      • 2014-04-15
      • 1970-01-01
      • 1970-01-01
      • 2021-02-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-18
      相关资源
      最近更新 更多