在ASP中进行锁定.净正确

本文关键字:锁定 ASP | 更新日期: 2023-09-27 17:49:24

我有一个ASP。. NET网站的搜索功能相当慢,我想通过使用查询作为缓存键将结果添加到缓存中一个小时来提高性能:

using System;
using System.Web;
using System.Web.Caching;
public class Search
{
    private static object _cacheLock = new object();
    public static string DoSearch(string query)
    {
        string results = "";
        if (HttpContext.Current.Cache[query] == null)
        {
            lock (_cacheLock)
            {
                if (HttpContext.Current.Cache[query] == null)
                {
                    results = GetResultsFromSlowDb(query);
                    HttpContext.Current.Cache.Add(query, results, null, DateTime.Now.AddHours(1), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
                }
                else
                {
                    results = HttpContext.Current.Cache[query].ToString();
                }
            }
        }
        else
        {
            results = HttpContext.Current.Cache[query].ToString();
        }
        return results;
    }
    private static string GetResultsFromSlowDb(string query)
    {
        return "Hello World!";
    }
}

假设访问者A进行搜索。缓存为空,锁被设置,结果从数据库请求。现在访问者B带着不同的搜索来了:访问者B不需要在锁旁等待,直到访问者a的搜索完成吗?我真正想要的是B立即调用数据库,因为结果将是不同的,数据库可以处理多个请求-我只是不想重复昂贵的不必要的查询。

对于这种情况,正确的方法是什么?

在ASP中进行锁定.净正确

除非您绝对确定没有冗余查询是至关重要的,否则我会完全避免锁定。ASP。. NET缓存本质上是线程安全的,因此以下代码的唯一缺点是,当关联的缓存条目过期时,您可能会暂时看到一些冗余查询相互竞争:

public static string DoSearch(string query)
{
    var results = (string)HttpContext.Current.Cache[query];
    if (results == null)
    {
        results = GetResultsFromSlowDb(query);
        HttpContext.Current.Cache.Insert(query, results, null,
            DateTime.Now.AddHours(1), Cache.NoSlidingExpiration);
    }
    return results;
}

如果你决定你真的必须避免所有冗余查询,那么你可以使用一组更细粒度的锁,每个查询一个锁:

public static string DoSearch(string query)
{
    var results = (string)HttpContext.Current.Cache[query];
    if (results == null)
    {
        object miniLock = _miniLocks.GetOrAdd(query, k => new object());
        lock (miniLock)
        {
            results = (string)HttpContext.Current.Cache[query];
            if (results == null)
            {
                results = GetResultsFromSlowDb(query);
                HttpContext.Current.Cache.Insert(query, results, null,
                    DateTime.Now.AddHours(1), Cache.NoSlidingExpiration);
            }
            object temp;
            if (_miniLocks.TryGetValue(query, out temp) && (temp == miniLock))
                _miniLocks.TryRemove(query);
        }
    }
    return results;
}
private static readonly ConcurrentDictionary<string, object> _miniLocks =
                                  new ConcurrentDictionary<string, object>();

你的代码有一个潜在的竞争条件:

if (HttpContext.Current.Cache[query] == null)         
{   
    ...
}         
else         
{
    // When you get here, another thread may have removed the item from the cache
    // so this may still return null.
    results = HttpContext.Current.Cache[query].ToString();         
}

一般情况下,我不会使用锁,为了避免竞争条件,我会这样做:

results = HttpContext.Current.Cache[query];
if (results == null)         
{   
    results = GetResultsFromSomewhere();
    HttpContext.Current.Cache.Add(query, results,...);
}
return results;

在上面的例子中,如果多个线程同时检测到缓存丢失,它们可能会尝试加载数据。在实践中,这很可能是罕见的,并且在大多数情况下不重要,因为它们加载的数据将是等效的。

但是如果你想使用锁来防止它,你可以这样做:

results = HttpContext.Current.Cache[query];
if (results == null)         
{   
    lock(someLock)
    {
        results = HttpContext.Current.Cache[query];
        if (results == null)
        {
            results = GetResultsFromSomewhere();
            HttpContext.Current.Cache.Add(query, results,...);
        }           
    }
}
return results;

您的代码正确。您还使用double-if-sandwitching-lock,它将防止竞争条件,这是不使用时的常见陷阱。这将不会对缓存中的现有内容进行锁访问。

唯一的问题是当许多客户端同时插入缓存时,它们将在锁后面排队,但我要做的是将results = GetResultsFromSlowDb(query);放在锁之外:

public static string DoSearch(string query)
{
    string results = "";
    if (HttpContext.Current.Cache[query] == null)
    {
        results = GetResultsFromSlowDb(query); // HERE
        lock (_cacheLock)
        {
            if (HttpContext.Current.Cache[query] == null)
            {

                HttpContext.Current.Cache.Add(query, results, null, DateTime.Now.AddHours(1), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
            }
            else
            {
                results = HttpContext.Current.Cache[query].ToString();
            }
        }
    }
    else
    {
        results = HttpContext.Current.Cache[query].ToString();
    }