c#如何创建线程安全的二维锁集合?

时间:2016-01-08 12:38:11

标签: c# .net multithreading locking

我试图实现一个线程安全的二维集合来控制对多个资源的访问。问题来自我所看到的我需要在集合级别(这很简单)和单个对象级别执行锁定,但不一定同时可能导致某些问题。

public class LockStore
{
    private object _syncLock = new object();
    private Dictionary<string, StoredLock> _locks = new Dictionary<string, StoredLock>();

    public StoredLock GetOrAdd(string id, StoredLock lockDetails)
    {
        if (!_locks.ContainsKey(id))
            lock (_syncLock)
            {
                if (!_locks.ContainsKey(id))
                    _locks.Add(id, lockDetails);
            }
        return _locks[id];
    }

    public void Remove(string id)
    {
        lock (_syncLock)
        {
            _locks.Remove(id);
        }
    }
}

public class StoredLock
{
    public string Id { get; set; }
    public DateTime CreatedDateTime { get; set; }
}
例如,一个线程可以调用LockStore.GetOrAdd()方法,返回一个StoredLock对象。然后,我想锁定StoreLock对象以防止其他线程访问它,但不保留整个字典的锁。

var id = "someId"
var storedLock = lockStore.GetOrAdd(id, new StoredLock());

lock (storedLock)
{
    //Do something
}

在某个地方完全不同的话......

lockStore.Remove(id);

在LockStore中存储的StoredLock对象上获取锁定时,LockStore不会保持锁定,因此理论上可以为该特定Id调用LockStore.Remove(),因为Remove()方法没有理解锁定在它实际包含的对象上。我不确定在这种情况下会发生什么,但我认为这不会很好!

另外,我认为这可能违反了用于锁定的对象无法公开访问的设计原则?

我真的很茫然,所以任何建议:解决问题或实际方法都会非常感激。

编辑:特别具体 - 我不想同时锁定LockStore集合和单独的StoredLock,因为这会大大减慢处理速度。

1 个答案:

答案 0 :(得分:1)

在理解了问题之后,我认为你应该做这样的事情:

public class LockStore
{
    private readonly Dictionary<string, StoredLock> m_Dictionary =
        new Dictionary<string, StoredLock>();

    public void UseResource(string resource_id, Action<StoredLock> action)
    {
        StoredLock stored_lock = null;

        lock(m_Dictionary)
        {
            if (m_Dictionary.ContainsKey(resource_id))
            {
                stored_lock = m_Dictionary[resource_id];
            }
            else
            {
                stored_lock = new StoredLock
                {
                    Id = resource_id,
                    CreatedDateTime = DateTime.Now
                };

                m_Dictionary.Add(resource_id,stored_lock);
            }
        }

        lock(stored_lock)
        {
            action(stored_lock);
        }
    }

    public bool RemoveLock(string resource_id)
    {
        lock (m_Dictionary)
        {
            if (!m_Dictionary.ContainsKey(resource_id))
                return true;

            var stored_lock = m_Dictionary[resource_id];

            bool taken = false;
            Monitor.TryEnter(stored_lock, ref taken);

            if (!taken)
                return false;

            try
            {
                m_Dictionary.Remove(resource_id);
            }
            finally
            {
                Monitor.Exit(stored_lock);
            }

            return true;
        }
    }
}

此代码设计不完美但有效。你应该努力使它变得更好。

基本上,这个类允许你从线程1做这样的事情:

lockStore.UseResource("resource1", sl => 
{
    //Do some processing with s1
});

从线程2中做这样的事情:

var removed = lockStore.RemoveLock("resource1");

RemoveLock将立即返回,如果某个线程仍然持有该对象的锁定,则返回值为false