锁定多个对象:场景和风险

时间:2015-05-18 07:55:56

标签: c# multithreading locking concurrentdictionary

我想从数据库加载一些对象并缓存它们。这很简单:

public class Dal {
    public Entity GetEntity(int id) {
        var cacheKey = string.Format(".cache.key.{0}", id);
        var item = Cache.Get(cacheKey) as Entity;
        if(item == null) {
            item = LoadEntityFromDatabase(id);
            Cache.Add(cacheKey, item);
        }
        return item;
    }
}

这很简单。但是当多个线程想要访问Dal.GetEntity时,我想lock一个与id有界的对象。我能想到的场景是这样的:

public class Dal {

    private static readonly ConcurrentDictionary<int, object>
        _locks = new ConcurrentDictionary<int, object>();

    public Entity GetEntity(int id) {
        var cacheKey = string.Format(".cache.key.{0}", id);
        var item = Cache.Get(cacheKey) as Entity;
        if(item == null) {
            var lockObject = _locks.GetOrAdd(id, new object());            
            lock (lockObject) {
                if(item == null) {
                    // all exceptions are handled in this block.
                    item = LoadEntityFromDatabase(id); 
                    Cache.Add(cacheKey, item);
                }
            }
            // * Here is the removing problem: 
            _locks.TryRemove(id, out lockObject);
        }
        return item;
    }
}

目标是:当一个线程从数据库中获取时,我想锁定id。实际上我想要防止从数据库请求一个对象两次。我觉得它似乎有效。但我不是一个多线程程序员,也不是lock人。所以,问题是:我对这种情况采取了哪些风险?

另外,我有这个问题:如何防止字典变得越来越大?如您所见, - 我标有* - 的地方,我正在尝试使用TryRemove方法删除已使用的项目。但这似乎是一个愚蠢的举动:如果它试图删除一个对象,而该对象被另一个线程锁定怎么办?我是对的吗?

2 个答案:

答案 0 :(得分:0)

我强烈建议你不要混用ConcurrentDictionary和锁 可以使用锁和普通词典,也可以使用ConcurrentDictionary和无锁 你目前正冒着比赛的风险 - 例如当3个线程作用于同一记录时。线程删除旧锁 - B线程使用旧锁,C线程创建新锁,以便B和C可以并行工作。

答案 1 :(得分:0)

这样的事情。 (未经测试);

public class Dal
{
    private sealed class Locker
    {
        private static readonly object DictionaryLocker = new object();
        private static readonly Dictionary<int, Locker>
            Locks = new Dictionary<int, Locker>();

        private readonly object _lockerObject;
        private int _num;

        private Locker()
        {
            _num = 0;
            _lockerObject = new object();
        }

        public static object StartLock(int id)
        {
            Locker locker;
            lock (DictionaryLocker)
            {
                if (!Locks.TryGetValue(id, out locker))
                {
                    locker = new Locker();
                    Locks.Add(id, locker);
                }
                ++locker._num;
            }

            return locker._lockerObject;
        }

        public static void EndLock(int id)
        {
            lock (DictionaryLocker)
            {
                Locker locker = Locks[id];

                --locker._num;

                if (locker._num == 0)
                    Locks.Remove(id);
            }
        }
    }

    public Entity GetEntity(int id)
    {
        var cacheKey = string.Format(".cache.key.{0}", id);
        var item = Cache.Get(cacheKey) as Entity;
        if (item != null)
            return item;

        object lockObject = Locker.StartLock(id);

        lock (lockObject)
        {
            // all exceptions are handled in this block.
            item = LoadEntityFromDatabase(id);
            Cache.Add(cacheKey, item);
            Locker.EndLock(id);
        }
        return item;
    }
}

编辑已更改为Dictionary