锁定以将数据加载到缓存

本文关键字:缓存 加载 数据 锁定 | 更新日期: 2023-09-27 18:27:19

我在一个web应用程序中有一个助手类,它所做的事情之一是将常见的、不变的数据对象作为静态属性呈现。我加载这些对象如下:

public static class MyWebHelper
{
    #region - Constants & Fields 
    private const string LocationCacheKey = "MyWebHelper_Locations";
    private static object LocationLoadLock = new object();
    private static MemoryCache m_SiteCache;
    #endregion
    #region - Properties 
    /// <summary>
    /// Gets the uneditable collection of locations.
    /// </summary>
    public static ReadOnlyCollection<Location> Locations
    {
        get
        {
            EnsureLocationsCache();
            return (ReadOnlyCollection<Location>)SiteCache[LocationCacheKey];
        }
    }
    /// <summary>
    /// Gets the MemoryCache object specific for my site cache.
    /// </summary>
    public static MemoryCache SiteCache
    {
        get
        {
            if (m_SiteCache == null)
            {
                m_SiteCache = new MemoryCache("MyWeb_SiteCache");
            }
            return m_SiteCache;
        }
    }
    #endregion
    #region - Methods 
    private static void EnsureLocationsCache()
    {
        lock (LocationLoadLock)
        {
            if (SiteCache[LocationCacheKey] == null)
            {
                //
                // Load all Locations from the database and perform default sorting on location code.
                //
                List<Location> lLocations = DataAccess.GetAllLocations();
                lLocations.Sort(delegate(Location l1, Location l2) { return string.CompareOrdinal(l1.Code, l2.Code); });
                SiteCache[LocationCacheKey] = new ReadOnlyCollection<Location>(lLocations);
            }
        }
    }
    #endregion
}

我的问题是,锁有帮助吗?我正在努力减少对数据库的调用,但锁定只是引入了开销吗?缓存的数据在整个网站上使用非常普遍,而且几乎永远不会改变。还有,我锁在正确的地方了吗?

锁定以将数据加载到缓存

如果你锁定,我会使用双重检查锁定,这样你就不会在每次从缓存读取时产生获取锁定的开销。

我也会质疑是否需要上锁。如果不锁定,那么多个线程可能会同时尝试刷新缓存,但这种情况很少见,除非非常昂贵,否则不会给静态只读数据带来问题。

如果昂贵的,那么您可以按照@asawyer评论中的建议,在缓存中插入一个Lazy<ReadOnlyCollection<Location>>,这样锁定将由Lazy<T>处理。

由于对服务器的每个web请求都在创建一个新线程,并且您的静态助手在这些线程之间共享,因此某种类型的锁很有用。如果没有锁,当数据库读取已经在进行时,您将面临进入EnsureLocationsCache方法的风险。现在,这可能不会影响两次加载的数据的正确性,但如果DB读取成本高昂,它将影响您的整体性能,并抵消缓存的影响。

这实际上取决于应用程序启动时尝试访问EnsureLocationsCache()方法的并发线程的数量,这可能很低,因为这是对每个LocationCacheKey的一次性调用。

获取锁的开销是一个值得关注的问题,因为即使缓存已经加载,代码也会获取锁@asawyer、@TomTom和@Joe提出了一些替代方案。

编辑:

请考虑在静态构造函数中调用EnsureLocationsCache()。在这种情况下,您根本不需要锁。

参见关于静态构造函数线程安全的讨论。