锁定集合与syncRoot的任何缺点?

时间:2010-10-11 23:22:22

标签: c# .net multithreading locking

我想知道锁定一个集合(例如List<T>HashSet<T>Dictionary<TKey, TValue>而不是简单的object是否有任何缺点。< / p>

注意:在以下示例中,这是锁定发生的唯一位置,它不会从多个位置锁定,但可以从多个线程调用静态方法。此外,永远不会在_dict方法之外访问GetSomething

我目前的代码如下:

private static readonly Dictionary<string, string> _dict = new Dictionary<string, string>();
public static string GetSomething(string key)
{
    string result;
    if (!_dict.TryGetValue(key, out result))
    {
        lock (_dict)
        {
            if (!_dict.TryGetValue(key, out result))
            {
                _dict[key] = result = CalculateSomethingExpensive(key);
            }
        }
    }
    return result;
}

另一位开发人员告诉我,锁定集合会导致问题,但我持怀疑态度。如果我这样做,我的代码会更有效吗?

private static readonly Dictionary<string, string> _dict = new Dictionary<string, string>();
private static readonly object _syncRoot = new object();
public static string GetSomething(string key)
{
    string result;
    if (!_dict.TryGetValue(key, out result))
    {
        lock (_syncRoot)
        {
            if (!_dict.TryGetValue(key, out result))
            {
                _dict[key] = result = CalculateSomethingExpensive(key);
            }
        }
    }
    return result;
}

5 个答案:

答案 0 :(得分:13)

如果您将收藏品暴露给外界,那么,这可能是一个问题。通常的建议是锁定您独有的东西,并且永远不会被影响范围之外的代码意外锁定。这就是为什么通常最好锁定你从未考虑过暴露的东西(即为此目的创建的特定锁对象)。这样,当你的记忆失败时,你将从不可能不会得到意想不到的结果。

更直接地回答你的问题:在混合中加入另一个对象永远不会更有效率,但是在一些被认为的,但未测量的效率之前放置通常被认为是良好编码实践的东西可能是过早发生的选择。我赞成最佳实践,直到它明显造成瓶颈。

答案 1 :(得分:6)

在这种情况下,我会锁定集合;锁的目的直接与集合有关,而不是与任何其他对象有关,因此在使用它作为锁对象时会有一定程度的自我注释。

我会做出改变。

我在文档中找不到任何说明TryGetValue是线程安全的并且如果在字典处于无效状态时调用它而不会抛出异常(或更糟),因为它是中途通过添加新值。因为它不是原子的,所以你在这里使用的双读模式(以避免获得锁定所花费的时间)并不安全。这将不得不改为:

private static readonly Dictionary<string, string> _dict = new Dictionary<string, string>();
public static string GetSomething(string key)
{
    string result;
    lock (_dict)
    {
        if (!_dict.TryGetValue(key, out result))
        {
            _dict[key] = result = CalculateSomethingExpensive(key);
        }
    }
    return result;
}

如果它可能涉及更多成功的读取而不是成功(因此需要写入),使用ReaderWriterLockSlim将为这些读取提供更好的并发性。

编辑:我刚刚注意到你的问题一般不是关于偏好,而是关于效率。实际上,在整个系统中使用4字节更多内存的效率差异(因为它是静态的)绝对为零。决定不是关于效率,而是因为两者具有相同的技术优点(在这种情况下)是关于你是否发现锁定集合或单独的对象更好地表达你的意图给另一位开发人员(包括你将来)。

答案 2 :(得分:3)

没有。只要变量无法从其他任何地方访问,并且您可以保证仅在此处使用锁定,则没有缺点。实际上,Monitor.Enter的文档(这是C#中使用的lock)就是这样做的。

但是,作为一般规则,我仍然建议使用私有对象进行锁定。这通常更安全,并且如果您将此对象暴露给任何其他代码,将保护您,因为您不会打开从其他代码锁定对象的可能性。

答案 3 :(得分:1)

直接回答您的问题:

无论你锁定什么对象都没有区别。 .NET只关心它的引用,它的工作方式与指针完全相同。考虑将.NET锁定为一个大的同步哈希表,其中键是对象引用,值是一个布尔表示你可以进入监视器。如果两个线程锁定到不同的对象(a!= b),它们可以同时进入锁的监视器,即使a.Equals(b)(这非常重要!!!)。但如果他们锁定a和b,并且(a == b)一次只有一个将在监视器中。

只要不在您的范围之外访问dict,就不会对性能产生影响。如果dict在其他地方可见,其他用户代码可能会锁定它,即使不一定需要(认为你的桌面是愚蠢的并锁定他在代码中找到的第一个随机对象)。

希望得到帮助。

答案 4 :(得分:0)

我建议使用ICollection.SyncRoot对象来锁定而不是你自己的对象:

    private static readonly Dictionary<String, String> _dict = new Dictionary<String, String>();
    private static readonly Object _syncRoot = ((ICollection)_dict).SyncRoot;