2010-12-08 102 views
1

好吧我有點不確定如何最好地命名這個問題:)但假設這個情況,你 出去,並提取一些網頁(與各種網址),並在本地緩存。即使使用多線程,緩存部分也很容易解決。併發緩存共享的模式

但是,想象一個線程開始提取一個url,幾毫秒後又想獲得相同的url。有沒有什麼好的模式讓第二個線程的方法等待第一個方法獲取頁面,將它插入到緩存中並返回,以便不必執行多個請求。即使對於需要大約300-700毫秒的請求,開銷也不夠大,值得做。而如果沒有經過對方鎖定爲其他URL

基本上請求時,對於相同的URL的請求進來緊密我想第二個請求,以「搭便車」的第一個請求

我有一本字典,你的一些鬆散的想法當您開始獲取頁面並鎖定頁面時,插入一個帶有密鑰的對象作爲url。如果已經有任何匹配的鍵,它將獲得該對象,鎖定該對象,然後嘗試獲取實際緩存的url。

我有點不確定的詳情然而,使其真正線程安全的,使用ConcurrentDictionary可能是它的一個組成部分......

是否有這樣的情況下任何共同的模式和解決方案?

擊穿錯誤的行爲:

線程1:檢查高速緩存,它不存在,所以開始取的URL

線程2:開始取相同的URL,因爲它仍然在緩存

不存在

線程1:完成的,並插入到緩存中,返回頁

線程2:表面處理,並且還插入到高速緩衝存儲器(或丟棄),返回頁

擊穿正確的行爲:

線程1:檢查高速緩存,它不存在這樣開始取的URL

線程2:想相同的URL,但看到它目前正在取出等線程1

等待

線程1:成品,並插入到緩存中,返回頁面

線程2:注意到線程1結束,返回頁主題1它取

編輯

大多數解決方案中辛勤似乎誤解了問題,只有解決了高速緩存,因爲我說的是心不是問題,問題做一個外部網絡時,取使第二取是前首先完成一個緩存了它使用第一個的結果而不是第二個

+0

我的回答*確實*解決您在編輯中提出的問題。 – LukeH 2010-12-09 09:59:32

+0

@Luke,你目前的解決方案似乎確實是我正在尋找的,謝謝!我將等待幾個小時的任何替代解決方案,然後我將結束這個問題 – Homde 2010-12-09 11:10:47

+0

您是否考慮過一種解決方案,您將使用某種同步字典(例如ConcurrentDictionary),並將url作爲關鍵字,以及類似IAsyncResult的內容值?如果線程2嘗試獲取線程1當前正在下載的頁面,則只需等待IAsyncResult,直到它完成並獲取頁面內容(IAsyncResult可能不是正確的選擇,但是您可以獲得理念...)。 – Mike 2010-12-11 01:36:30

回答

1

你可以使用一個ConcurrentDictionary<K,V>double-checked locking變體:

public static string GetUrlContent(string url) 
{ 
    object value1 = _cache.GetOrAdd(url, new object()); 

    if (value1 == null) // null check only required if content 
     return null;  // could legitimately be a null string 

    var urlContent = value1 as string; 
    if (urlContent != null) 
     return urlContent; // got the content 

    // value1 isn't a string which means that it's an object to lock against 
    lock (value1) 
    { 
     object value2 = _cache[url]; 

     // at this point value2 will *either* be the url content 
     // *or* the object that we already hold a lock against 
     if (value2 != value1) 
      return (string)value2; // got the content 

     urlContent = FetchContentFromTheWeb(url); // todo 
     _cache[url] = urlContent; 
     return urlContent; 
    } 
} 

private static readonly ConcurrentDictionary<string, object> _cache = 
            new ConcurrentDictionary<string, object>(); 
0

請問Semaphore請站起來!站起來!站起來!

使用Semaphore你可以很容易地與你的線程同步。 在兩種情況下

  1. 你要加載當前正在緩存
  2. 要保存緩存到一個頁面是由它加載文件的頁面。

在這兩種情況下你都會遇到麻煩。

這就像操作系統賽車問題中常見的作家和讀者問題一樣。就在一個線程想要重建一個緩存或者開始緩存一個頁面時,線程不應該讀取它。如果一個線程正在讀取它,它應該等到讀取完成並更換緩存,否則2個線程應該將相同的頁面緩存到同一個文件中。因此所有讀者都可以在任何時候從緩存中讀取數據,因爲沒有作者正在撰寫它。

你應該使用MSDN上的示例閱讀一些信號量,它非常易於使用。只是想要做某事的線程就是調用信號量,如果資源可以授予它的話,那麼它就會休眠並等待在資源準備就緒時喚醒。

1

編輯:我的代碼現在比較醜陋,但每個URL使用一個單獨的鎖。這允許不同的URL被異步提取,但是每個URL只會被提取一次。

public class UrlFetcher 
{ 
    static Hashtable cache = Hashtable.Synchronized(new Hashtable()); 

    public static String GetCachedUrl(String url) 
    { 
     // exactly 1 fetcher is created per URL 
     InternalFetcher fetcher = (InternalFetcher)cache[url]; 
     if(fetcher == null) 
     { 
      lock(cache.SyncRoot) 
      { 
       fetcher = (InternalFetcher)cache[url]; 
       if(fetcher == null) 
       { 
        fetcher = new InternalFetcher(url); 
        cache[url] = fetcher; 
       } 
      } 
     } 
     // blocks all threads requesting the same URL 
     return fetcher.Contents; 
    } 

    /// <summary>Each fetcher locks on itself and is initilized with null contents. 
    /// The first thread to call fetcher.Contents will cause the fetch to occur, and 
    /// block until completion.</summary> 
    private class InternalFetcher 
    { 
     private String url; 
     private String contents; 

     public InternalFetcher(String url) 
     { 
      this.url = url; 
      this.contents = null; 
     } 

     public String Contents 
     { 
      get 
      { 
       if(contents == null) 
       { 
        lock(this) // "this" is an instance of InternalFetcher... 
        { 
         if(contents == null) 
         { 
          contents = FetchFromWeb(url); 
         } 
        } 
       } 
       return contents; 
      } 
     } 
    } 
} 
0

免責聲明:這可能是一個完美的答案。請原諒我,如果是的話。

我建議使用一些帶鎖的共享字典對象來跟蹤當前提取或已經獲取的url的軌跡。

  • 在每個請求中,檢查此對象的url。

  • 如果存在url的條目,請檢查緩存。 (這意味着其中一個線程已經提取或正在提取它)

  • 如果它在緩存中可用,請使用它,否則將當前線程休眠一段時間並再次檢查。 (如果不在緩存中,某個線程仍在提取它,所以請等待它完成)

  • 如果在字典對象中找不到該條目,請向其中添加url併發送請求。一旦獲得響應,將其添加到緩存。

此邏輯應該可以工作,但是,您需要照顧緩存過期並從字典對象中刪除條目。