这是锁定线程安全的正确对象吗?

时间:2010-09-29 13:03:33

标签: c# multithreading thread-safety

下面的代码将从网站调用,因此在非静态类中拥有静态字典对象需要是线程安全的。基本上,代码的目的是封装逻辑并维护perfmon计数器的生命周期,这些计数器存储在CounterContainer的实例中。构造函数被称为传入instanceName。构造函数需要检查是否已定义该instanceName的CounterContainer并将其存储在字典中。如果是这样,它可以(并且必须)使用该实例。如果没有,它会创建CounterContainer的实例,将其存储在字典中,然后使用该实例。要使用的CounterContainer的实例存储在非静态成员中,因此从那时起是线程安全的。

作为静态字典使用的唯一地方是在构造函数中,我觉得在将要访问它的时间内锁定字典是安全的吗?这是否会导致以后出现任何无法预料的问题,例如阻塞/死锁?我看不到任何东西,但过去并没有真正需要过多考虑这类事情。

我还考虑过 lock(this):,但我认为这不会起作用,因为它只会锁定正在创建的PerformanceCounters的实例,而不是基础静态字典(所以不会是线程安全的)。

namespace ToolKit
{
    using System;
    using System.Diagnostics;
    using System.Collections.Generic;

    public class PerformanceCounters : IPerformanceCounters
    {
        private static Dictionary<string, CounterContainer> _containers = new Dictionary<string, CounterContainer>();
        private CounterContainer _instanceContainer;

        public PerformanceCounters(string instanceName)
        {
            if (instanceName == null) throw new ArgumentNullException("instanceName");
            if (string.IsNullOrWhiteSpace(instanceName)) throw new ArgumentException("instanceName");

            // Is this the best item to lock on?
            lock (_containers)
            {

                if (_containers.ContainsKey(instanceName))
                {
                    _instanceContainer = _containers[instanceName];
                    return;
                }

                _instanceContainer = new CounterContainer(instanceName);
                _containers.Add(instanceName, _instanceContainer);
            }
        }
        public void Start()
        {
            _instanceContainer.AvgSearchDuration.Start();
        }

        public void FinishAndLog()
        {
            _instanceContainer.SearchesExecuted.Increment();
            _instanceContainer.SearchesPerSecond.Increment();
            _instanceContainer.AvgSearchDuration.Increment();
        }
    }
}

4 个答案:

答案 0 :(得分:5)

不要不同意使用ConcurrentDictionary的建议,而是更广泛地回答:

  1. 最好的锁定解决方案是根本不锁定的解决方案。有时在没有锁定的情况下制作并发的东西并不是很困难。接下来最好的是有细粒度的锁。但是,粗粒度锁更易于推理,因此对其正确性充满信心。除非你有一个经过良好测试的无锁类适合用途,否则从粗粒度锁(用同一个锁阻止一大堆操作的锁)开始,然后移动到更细粒度,因为有逻辑死锁(您可以看到两个不相关的操作可能会相互阻塞)或者根据需要进行优化。
  2. 永远不要锁定this,因为该代码外部的东西可以锁定对象,并且您可以轻松地获得死锁或至少锁定争用,只能通过查看类内部的代码才能找到和调用代码(和写一个人可能无法访问另一个)。出于类似原因,永远不要锁定Type
  3. 出于类似的原因,只能锁定私人会员。这样只有类内部的代码才能锁定它,并且锁争用的可能性仅限于那个地方。
  4. 避免对此类代码使用partial,如果使用partial,请尝试将对象上的所有锁保留在同一位置。这里没有任何技术原因,但是当你需要考虑可以采取锁定的所有潜在地点时,它确实有帮助。
  5. 永远不要锁定值类型(整数类型,结构等)。这样做会将值设置为新对象并锁定它。然后,当其他一些代码试图获得锁定时,它会将另一个对象锁定并锁定它。基本上没有任何锁定(除非理论上优化,拳击使用轻量级模式[它没有],但实际上会使事情变得更糟)。
  6. 有时,锁的用途与使用锁时使用的单个对象有关,而在未使用锁时则不使用。在这种情况下,使用此对象作为锁定的对象有助于将锁与该对象相关联的代码的可读性。
  7. 让一个纯粹出于锁定目的而存在的对象永远不会错,所以如果你不确定,那就去做吧。
  8. 如果受影响的任何对象是静态的,则锁定对象必须是静态的,否则可能是实例。相反,由于实例锁定比静态锁定更细粒度,因此实例在适用时更好。

答案 1 :(得分:2)

是的,它将起作用,因为_containers是静态的。

你可能也想看看ReaderWriterLockSlim,所以你想要阻止这么多(改善性能)

答案 2 :(得分:0)

在我看来,拥有一个专门用于锁定的显式对象实例是很常见的。

private readonly object containerLock = new object();
...
lock (containerLock) {
 ...
}

另一个提示:如果您正在使用.NET 4,请考虑使用ConcurrentDictionary

答案 3 :(得分:0)

是的,_containers在理论上是可以的,因为它的范围与字典实例相同,也就是说,它是静态的(显然 - 它是字典实例。)

然而,一旦您对整体设计感到担忧,您就会说这是在网站上托管的。每个asp.net worker都是一个单独的进程,因此当IIS由于空闲或其他原因而回收工作进程时,您的静态字典可能会被销毁。此外,如果您使用网络花园(每个应用程序有多个工作人员),您可能有多个字典实例。