确保单个创建缓存项目的缓存

我想要创建一个缓存,以便当一个项目不存在时,只有一个请求该项目的人花费时间来产生它,而另一些人同时请求它将被阻塞,直到结果被第一个人缓存为止。 以下是该场景的描述:

  • 线程1进来并请求为DateA缓存的数据
  • 线程1发现它不在缓存中并开始生成信息的相对较长的过程
  • 线程2进入一个看到,目前正在生成的信息,等待某种锁
  • 线程3也进来,看到信息正在生成,等待某种锁
  • 线程4进来并请求已经在缓存中的不同密钥的数据,并且根本不等待
  • 线程1完成生成并使用该值更新缓存
  • 线程2和3都会唤醒并获取现在在缓存中的结果
  • 我想我会有一个ConcurrentDictionary中的缓存与一个DateTime只存储日期作为关键像ConcurrentDictionary<DateTime, CustomImmutableObject> _cache;

    但是我看不到Thread 2和Thread 3能够等待什么。 即使存在另一个存储某种状态标志的ConcurrentDictionary,他们如何知道线程1何时完成?

    有没有人有任何建议如何处理开发这样的缓存?


    您可以使用ConcurrentDictionary<DateTime, Lazy<CustomImmutableObject>>获取对象,如下所示:

    myDictionary
        .GetOrAdd(
            someDateTime,
            dt => new Lazy<CustomImmutableObject>(
                () => CreateMyCustomImmutableObject()
            )
        ).Value
    

    Lazy<T>在(默认的)线程安全模式下所做的保证将确保只发生一次初始化,并且实际被实例化之前的后续访问将被阻止。

    在多线程场景中,访问线程安全的Lazy(Of T)对象的Value属性的第一个线程会将其初始化为所有线程上的所有后续访问,并且所有线程共享相同的数据。 因此,哪个线程初始化对象并且竞争条件是否良好并不重要。

    详情请看这里。

    下面是我写的一个测试,以确保我不会吐痰。 这应该说服你,一切都很好:

    void Main()
    {
        var now=DateTime.UtcNow;
        var d=new ConcurrentDictionary<DateTime, Lazy<CustomImmutableObject>>();
        Action f=()=>{
            var val=d
                .GetOrAdd(
                    now,
                    dt => new Lazy<CustomImmutableObject>(
                        () => new CustomImmutableObject()
                    )
                ).Value;
            Console.WriteLine(val);
        };
        for(int i=0;i<10;++i)
        {
            (new Thread(()=>f())).Start();
        }
        Thread.Sleep(15000);
        Console.WriteLine("Finished");
    }
    
    class CustomImmutableObject
    {
        public CustomImmutableObject()
        {
            Console.WriteLine("CREATING");
            Thread.Sleep(10000);
        }
    }
    

    每次有人访问缓存对象时,您都可以对缓存对象进行lock ,如果缓存未命中,请对某个对象执行Monitor.Enter以指示Monitor.Enter对象创建,然后释放第一个锁。 创建完成后,对第二个锁对象使用Monitor.Exit

    缓存访问通常锁定在主对象上,但创建锁定在第二个对象上。 如果您希望并行创建,则可以在字典中创建一个锁对象,其中的密钥与高速缓存的密钥相同。


    我提出了以下似乎可行的方法,但是以锁定每次读取缓存为代价。 假设只有一个人可以一次为给定密钥添加cacheData数据是否安全?

    static ConcurrentDictionary<DateTime, object> cacheAccess = new ConcurrentDictionary<DateTime, object>();
    static ConcurrentDictionary<DateTime, int> cacheData = new ConcurrentDictionary<DateTime, int>();
    
    static int GetValue(DateTime key)
    {
        var accessLock = cacheAccess.GetOrAdd(key, x =>  new object());
    
        lock (accessLock)
        {
            int resultValue;
            if (!cacheData.TryGetValue(key, out resultValue))
            {
                Console.WriteLine("Generating {0}", key);
                Thread.Sleep(5000);
                resultValue = (int)DateTime.Now.Ticks;
                if (!cacheData.TryAdd(key, resultValue))
                {
                    throw new InvalidOperationException("How can something else have added inside this lock?");
                }
            }
    
            return resultValue;
        }
    }
    
    
    static void Main(string[] args)
    {
        var keys = new[]{ DateTime.Now.Date, DateTime.Now.Date.AddDays(-1), DateTime.Now.Date.AddDays(1), DateTime.Now.Date.AddDays(2)};
        var rand = new Random();
    
        Parallel.For(0, 1000, (index) =>
            {
                var key = keys[rand.Next(keys.Length)];
    
                var value = GetValue(key);
    
                Console.WriteLine("Got {0} for key {1}", value, key);
            });
    }
    
    链接地址: http://www.djcxy.com/p/62995.html

    上一篇: Cache that guarantees single creation of cached item

    下一篇: Options for caching / memoization / hashing in R