我的一个程序中有一个缓存系统。我有一个维护此缓存的静态类,并且同时在多个线程中使用缓存。我遇到了正确维护缓存系统的问题。这是一些示例代码。
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%肯定,但我能想到的唯一方法),是:
cur_cache_
会复制到old_cache_
。cur_cache_
已替换为new_cache
,导致旧cur_cache_
(old_cache_
当前指向的内容)为空。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
被删除了。
答案 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_
,并且很乐意接受能够解释这一点的人的答案。