我可以摆脱这个读锁吗?

时间:2011-01-11 06:23:23

标签: c# .net .net-2.0 locking

我有以下助手类(简化):

public static class Cache
{
    private static readonly object _syncRoot = new object();
    private static Dictionary<Type, string> _lookup = new Dictionary<Type, string>();

    public static void Add(Type type, string value)
    {
        lock (_syncRoot)
        {
            _lookup.Add(type, value);
        }
    }

    public static string Lookup(Type type)
    {
        string result;

        lock (_syncRoot)
        {
            _lookup.TryGetValue(type, out result);
        }

        return result;
    }
}

Add将在应用程序中大约调用10/100次,Lookup将被许多线程调用,数千次。我想要的是摆脱读锁。

在这种情况下,你通常如何摆脱读锁?

我有以下想法:

  1. 要求_lookup在应用程序开始运行之前保持稳定。可以从Attribute构建。这是通过分配属性的静态构造函数自动完成的。要求上述内容需要我查看所有可能具有该属性的类型并调用RuntimeHelpers.RunClassConstructor这是一项昂贵的操作;

  2. 转到COW语义。

    public static void Add(Type type, string value)
    {
        lock (_syncRoot)
        {
            var lookup = new Dictionary<Type, string>(_lookup);
    
            lookup.Add(type, value);
    
            _lookup = lookup;
        }
    }
    

    (在lock (_syncRoot)方法中删除Lookup。)问题是这会占用不必要的内存量(这可能不是问题)而且我可能会_lookup 1}} volatile,但我不确定应该如何应用它。 (John Skeets' comment here gives me pause.

  3. 使用ReaderWriterLock我认为这会让事情变得更糟,因为被锁定的区域很小。
  4. 建议非常欢迎。

    更新

    缓存的值是不可变的。

3 个答案:

答案 0 :(得分:1)

要完全删除锁(略微不同,然后“锁定自由”,其中几乎消除了锁,其余的被巧妙地替换为Interlocked指令),您需要确保您的字典是不可变的。如果字典中的项不是不可变的(并且因此它们拥有自己的锁),您可能不必担心在字典级别上锁定。

    如果你能使用它,
  1. 是最好和最简单的解决方案。
  2. 合理且易于调试。 (注意:如同编写的那样,对于并​​发添加相同项目不起作用。如果需要,请考虑双重检查锁定模式 - Double-checked locking in .NET
  3. 如果1/2是一个选项,我不会这样做。
  4. 如果您可以使用新的4.0集合 - ConcurrentDictionary符合您的条件(请参阅http://msdn.microsoft.com/en-us/library/dd997305.aspxhttp://blogs.msdn.com/b/pfxteam/archive/2010/01/26/9953725.aspx)。

答案 1 :(得分:0)

此刻的工作,没有什么优雅,想出了这个(未经测试的)

public static class Cache
{
    private static readonly object _syncRoot = new object();
    private static Dictionary<Type, string> _lookup = new Dictionary<Type, string>();

    public static class OneToManyLocker
    {
        private static readonly Object WriteLocker = new Object();
        private static readonly List<Object> ReadLockers = new List<Object>();
        private static readonly Object myLocker = new Object();

        public static Object GetLock(LockType lockType)
        {
            lock(WriteLocker)
            {
                if(lockType == LockType.Read)
                {
                    var newReadLocker = new Object();
                    lock(myLocker)
                    {
                        ReadLockers.Add(newReadLocker);
                    }
                    return newReadLocker;
                }

                foreach(var readLocker in ReadLockers)
                {
                    lock(readLocker) { }
                }

                return WriteLocker;
            }
        }

        public enum LockType {Read, Write};
    }

    public static void Add(Type type, string value)
    {
        lock(OneToManyLocker.GetLock(OneToManyLocker.LockType.Write))
        {
            _lookup.Add(type, value);
        }
    }

    public static string Lookup(Type type)
    {
        string result;
        lock (OneToManyLocker.GetLock(OneToManyLocker.LockType.Read))
        {
            _lookup.TryGetValue(type, out result);      
        }

        return result;
    }
}

你需要对读取锁定器进行某种清理,但是应该是线程安全的,允许一次多次读取,同时锁定写入,除非我完全遗漏了某些东西

答案 2 :(得分:0)

或者: