2010-12-08 91 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并发送请求。一旦获得响应,将其添加到缓存。

此逻辑应该可以工作,但是,您需要照顾缓存过期并从字典对象中删除条目。