2015-02-23 128 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_爲空。這似乎在第二次收到更新消息時發生。我假設發生了什麼(不是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()後,我得到我想要的矢量,並在矢量上進行迭代。對於每個字符串,我嘗試獲取第一個字符。當我嘗試獲取字符時,程序崩潰。我唯一能想到的就是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_的較後行之間存在一個窗口,其中客戶將看到從get_cache返回的nullptr。如果你絕對要保持在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 
} 
+0

大,謝謝你的'atomic_exchange'功能。至於第二部分,那原本就是我的,但我遇到了一個問題。我將用我的程序正在做的一些示例代碼更新原始帖子。我想我的問題是,是否有任何理由可以想到,在使用'cur_cache'的線程完成運行之前,緩存將被刪除。每次調用get_cache()時,我的'get_cache()'函數是否應該不增加'shared_ptr'的use_count? – Eumcoz 2015-02-24 14:14:29

+1

@Eumcoz'get_cache'返回'shared_ptr'的一個副本,它*增加了'use_count'。客戶端代碼中的問題是,它會刪除'shared_ptr'的副本 - 在訪問'shared_ptr'指向的對象的內部之前,減少'use_count' - * *。 – Casey 2015-02-24 16:54:47

+0

真棒,謝謝你的幫助,很有道理! – Eumcoz 2015-02-24 21:52:06

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增加至兩個,第一個電話後,第二個電話去後回落到1。

我仍然認爲有一個更好的方式來構建我的代碼,而不必保留old_cache_,並會很樂意接受某人可以解釋的答案。