2015-02-23 2 views
1

У меня есть система кеширования в одной из моих программ. У меня есть один статический класс, который поддерживает этот кеш и одновременно использует кеш в нескольких потоках. Я столкнулся с проблемой правильной работы системы кэширования. Вот пример кода.Как правильно перемещать/читать из 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_ - null. Это происходит во второй раз, когда получено сообщение об обновлении. Я предполагаю, что происходит (не 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_, что должно решить проблему выше.

Edit:

Вот код, использующий кэш-памяти:

//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

У вас есть гонка данных в 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 
} 
+0

замечательные, спасибо за 'atomic_exchange' функции , Что касается второй части, это было изначально то, что у меня было, но у меня возникла проблема. Я обновляю исходное сообщение с помощью некоторого примерного кода того, что делает моя программа. Думаю, мой вопрос в том, есть ли какая-то причина, по которой вы можете думать, что кеш будет удален до того, как мои потоки с помощью 'cur_cache' будут закончены. Должна ли моя функция get_cache() 'не увеличивать значение use_count для' shared_ptr' каждый раз, когда вызывается 'get_cache()'? – Eumcoz

+1

@Eumcoz 'get_cache' возвращает копию' shared_ptr', которая * * увеличивает свой 'use_count'. Проблема в клиентском коде заключается в том, что она удаляет эту копию 'shared_ptr' - уменьшает значение' use_count' - * before *, обращаясь к внутренним объектам, на которые указывал 'shared_ptr'. – Casey

+0

Удивительно, спасибо вам за помощь, имеет смысл! – Eumcoz

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_ «s увеличение use_count до двух, после первого звонка, и вернуться обратно вниз до 1 после второго вызова.

Я по-прежнему считаю, что есть лучший способ структурировать мой код, не оставляя old_cache_, и был бы рад принять ответ от того, кто может это объяснить.

Смежные вопросы