Synchronize threads on per-item base

廉价感情. 提交于 2019-12-11 12:29:14

问题


While this question is about the MemoryCache class, I can imagine the same need with a Dictionary or ConcurrentDictionary.GetOrAdd where the valueFactory-lambda is also a lengthy operation.

In essence I want to synchronize/lock threads on a per-item base. I know MemoryCache is thread safe, but still, checking if an item exists and add the item when it doesn't exist, still needs to be synchronized.

Consider this sample code:

public class MyCache
{
    private static readonly MemoryCache cache = new MemoryCache(Guid.NewGuid().ToString());

    public object Get(string id)
    {
        var cacheItem = cache.GetCachedItem(id);
        if (cacheItem != null) return cacheItem.Value;
        var item = this.CreateItem(id);
        cache.Add(id, item, new CacheItemPolicy
        {
            SlidingExpiration = TimeSpan.FromMinutes(20)
        });
        return item;
    }

    private object CreateItem(string id)
    {
        // Lengthy operation, f.e. querying database or even external API
        return whateverCreatedObject;
    }
}

As you can see, we need to synchronize cache.GetCachedItem and cache.Add. But since CreateItem is a lengthy operation (hence the MemoryCache), I don't want to lock all threads as this code would do:

public object Get(string id)
{
    lock (cache)
    {
        var item = cache.GetCachedItem(id);
        if (item != null) return item.Value;
        cache.Add(id, this.CreateItem(id), new CacheItemPolicy
        {
            SlidingExpiration = TimeSpan.FromMinutes(20)
        });
    }
}

Also, having no lock is not an options, as then we could have multiple threads calling CreateItem for the same id.

What I could do is create a unique named Semaphore per id, so locking happens on per-item basis. But this will be a system-resource killer, as we do not want to register +100K named semaphores on our system.

I'm sure I'm not the first that needs this kind of synchronization, but I didn't find any question/answer that fits this scenario.

My question is if someone can come up with a different, resource friendly approach for this problem?

Update

I've found this NamedReaderWriterLocker class that looks promising at first but is dangerous to use as two threads can potentially get a different ReaderWriterLockSlim instance for the same name when both threads get into the ConcurrentDictionary's valueFactory at the same time. Maybe I can use this implementation with some additional lock inside the GetLock method.


回答1:


Since your key is a string, you could lock on string.Intern(id).

MSDN documentation: System.String.Intern

i.e.

lock (string.Intern(id))
{
    var item = cache.GetCachedItem(id);
    if (item != null)
    {
        return item.Value;
    }

    cache.Add(id, this.CreateItem(id), new CacheItemPolicy
    {
        SlidingExpiration = TimeSpan.FromMinutes(20)
    });

   return /* some value, this line was absent in the original code. */;
}


来源:https://stackoverflow.com/questions/36839135/synchronize-threads-on-per-item-base

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!