如何从 shared_ptr 中正确地 move/read

How to properly move/read from a shared_ptr

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

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() 之后,我得到了我想要的向量,并对该向量进行了迭代。对于每个字符串,我尝试获取第一个字符。当我尝试获取 char 时,程序崩溃了。唯一能想到的就是selected_vec被删了

我想我可以为我的崩溃问题提供一个答案,尽管我仍然认为有更好的方法来设计解决方案。基本上,我的三步列表是正确的,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_,并且很乐意接受可以解释这一点的人的回答。

您在 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_ 的行之间有一个 window,客户将在其中看到返回的 nullptr来自 get_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))
    );
}

您的原始问题来源在此 "client" 代码中:

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
}