2014-03-25 115 views
0

我目前有一個程序,它具有緩存機制。我有一個線程監聽從另一個服務器到這個緩存的更新。該線程在收到更新時將更新緩存。下面是一些僞代碼:更新緩存而不會阻止

void cache::update_cache() 
{ 
    cache_ = new std::map<std::string, value>(); 
    while(true) 
    { 
     if(recv().compare("update") == 0) 
     { 
      std::map<std::string, value> *new_info = new std::map<std::string, value>(); 
      std::map<std::string, value> *tmp; 
      //Get new info, store in new_info 
      tmp = cache_; 
      cache_ = new_cache; 
      delete tmp;    
     } 
    } 
} 

std::map<std::string, value> *cache::get_cache() 
{ 
    return cache_; 
} 

cache_正在從許多不同的線程讀取兼任。我相信我是如何擁有它的,如果我的一個線程調用get_cache(),然後我的緩存更新,然後線程嘗試訪問存儲的緩存,我將遇到未定義的行爲。

我正在尋找一種方法來避免此問題。我知道我可以使用互斥鎖,但是我寧願不阻止讀取,因爲它們必須儘可能低延遲,但如果需要,我可以走這條路。

我在想這是不是一個unique_ptr的好用例。我的理解是正確的:如果一個線程調用get_cache,並且返回一個unique_ptr而不是一個標準指針,那麼一旦所有具有舊版本緩存的線程都完成了它(即保留範圍),該對象將被刪除。

使用unique_ptr這種情況下的最佳選擇,還是有另一種選擇,我沒有想到?

任何輸入將不勝感激。

編輯:

我相信我在我的OP中犯了一個錯誤。我的意思是使用並傳遞一個shared_ptr而不是cache_的unique_ptr。當所有線程都完成cache_時,shared_ptr應該刪除它自己。

有關我的程序:我的程序是一個網絡服務器,將使用這些信息來決定返回什麼信息。這是相當高的吞吐量(數千次請求/秒)每個請求查詢緩存一次,所以告訴我的其他線程何時更新沒有問題。我可以容忍稍微過時的信息,並希望在可能的情況下阻止我的所有線程執行。緩存中的信息相當大,因此我想限制任何有價值的副本。

update_cache只運行一次。它運行在只監聽更新命令並運行代碼的線程中。

+1

使用[讀者,作家鎖(http://en.wikipedia.org/wiki/Readers-writer_lock)。 [升壓提供酮](http://stackoverflow.com/questions/989795/example-for-boost-shared-mutex-multiple-reads-one-write/6450576#6450576)。此外,您可能能夠使用[從此處](http://stackoverflow.com/a/22492335/1074413)使用RCU的相同建議也適用。 –

+0

儘管如此,如果可能的話,我想避免阻塞。使用唯一指針會導致某種未定義的行爲嗎?讀/寫鎖是否是一個更好的解決方案是否有原因?另外一個說明,我可以容忍稍微過時的信息。 – Eumcoz

+1

我想我在這裏的處境實際上與設計有關,而不是實現。首先,'unique_ptr'看起來不起作用,因爲只有一個線程合法地可以保存'unique_ptr',這使得可以訪問緩存的多個線程失效。即使你可以解決這個問題,客戶端線程如何知道他們需要更新?我覺得你的緩存應該暴露一個接口,而不是僅僅在客戶端上傾倒實際的緩存指針,並讓它們瘋狂運行。如果您可以讓客戶端查詢緩存,那麼類似RCU的方法可能是完美的 - 零開銷讀取! –

回答

1

shared_ptr是非常合理的用於此目的的,C++ 11具有a family of functions for handling shared_ptr atomically。如果在創建後的數據是不可變的,你甚至不需要任何額外的同步:

class cache { 
public: 
    using map_t = std::map<std::string, value>; 
    void update_cache(); 
    std::shared_ptr<const map_t> get_cache() const; 
private: 
    std::shared_ptr<const map_t> cache_; 
}; 

void cache::update_cache() 
{ 
    while(true) 
    { 
     if(recv() == "update") 
     { 
      auto new_info = std::make_shared<map_t>(); 
      // Get new info, store in new_info 
      // Make immutable & publish 
      std::atomic_store(&cache_, 
           std::shared_ptr<const map_t>{std::move(new_info)}); 
     } 
    } 
} 

auto cache::get_cache() const -> std::shared_ptr<const map_t> { 
    return std::atomic_load(&cache_); 
} 
+0

謝謝,我相信這就是我一直在尋找的東西。不幸的是,我的編譯器不支持共享指針(運行GCC 4.7.2)的std :: atomic_store/atomic_load,並且我無法升級我的編譯器(在CentOS上運行)。我翻譯了一遍以提高等值。它似乎工作到目前爲止。 – Eumcoz

2

我覺得有多個問題:

1)不要泄漏內存:爲從未在代碼中使用「刪除」並用的unique_ptr(或shared_ptr的在特定情況下)

2棒)保護訪問共享數據,對於任一使用的鎖定(互斥)或無鎖mecanism(標準::原子)

class Cache { 
    using Map = std::map<std::string, value>(); 
    std::unique_ptr<Map> m_cache; 
    std::mutex m_cacheLock; 
public: 

    void update_cache() 
    { 
     while(true) 
     { 
      if(recv().compare("update") == 0) 
      { 
       std::unique_ptr<Map> new_info { new Map }; 
       //Get new info, store in new_info 
       { 
        std::lock_guard<std::mutex> lock{m_cacheLock}; 
        using std::swap; 
        swap(m_cache, new_cache); 
       } 
      } 
     } 
    } 

注:我不喜歡update_cache()是一個公共接口的一部分它包含一個緩存無限循環。我可能會與外部化的recv的循環,有:

void update_cache(std::unique_ptr<Map> new_info) 
    { 
     { // This inner brace is not useless, we don't need to keep the lock during deletion 
      std::lock_guard<std::mutex> lock{m_cacheLock}; 
      using std::swap; 
      swap(m_cache, new_cache); 
     } 
    } 

現在的讀數緩存,使用適當的封裝和不離開指針成員地圖逃生:

value get(const std::string &key) 
    { 
     // lock, fetch, and return. 
     // Depending on value type, you might want to allocate memory 
     // before locking 
    } 

使用此簽名,如果該值不存在於緩存中,則必須拋出異常,另一種選擇是返回類似於boost :: optional的內容。

總體可以保持低時延(一切都是相對的,我不知道你的使用情況),如果你照顧好做昂貴的操作(比如內存分配)的鎖定部分之外。

+0

這種挑釁看起來是可行的。請看到我的編輯用於小在這個基礎上,你是否看到任何理由將循環外部化?你的鎖和交換看起來像是可行的。我沒有添加接口來獲取值的原因有兩個原因:1)我不想按值返回值,它是一個大對象,我想通過引用返回。 2)如果我返回一個值的引用,然後交換指針,我相信該引用將導致未定義的行爲。我相信我在我的第一篇文章中犯了一個錯誤,我應該使用並返回cache_的shared_ptr。 – Eumcoz

+1

是的,如果值是一個大對象,你必須避免複製,那麼返回一個shared_ptr可能會更好。也可以讓你的緩存定義的std ::地圖<的std :: string,的std :: shared_ptr的>,並從獲取返回的std :: shared_ptr的。 我仍然沒有這個名字的線程方法作爲緩存接口的一部分,我將分成兩部分。我想知道的是,爲什麼緩存更新不是增量式的:一次只增加/刪除一個條目,而不是使全新緩存無效。特別是對於你提到的用例。 – Joky

+0

這是我的數據的性質。我的數據有一種情況,單個元素可以從一個鍵失效,或者一組元素可以從多個鍵無效。重要組織內部數據到預先查詢的桶中,所以我並不總是知道哪個桶在哪個桶中。值也是一組必須迭代的數據,保持我的所有密鑰更新的開銷將超過重新查詢數據的簡單性。我想我可以使用一個接口返回一個shared_ptr值,它聽起來很整潔,因爲線程一次只使用一個鍵。 – Eumcoz