Skip to main content
 首页 » 编程设计

c#之这是为了线程安全而锁定的正确对象吗

2024年01月25日32leader

下面的代码将从网站调用,因此在非静态类中有一个静态字典对象需要是线程安全的。代码的主要目的是封装逻辑并维护存储在 CounterContainer 实例中的 perfmon 计数器的生命周期。调用构造函数传入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(); 
        } 
    } 
} 

请您参考如下方法:

不是不同意使用 ConcurrentDictionary 的建议,而是更笼统地回答:

  1. 最好的锁定解决方案是完全不锁定的解决方案。有时在没有锁定的情况下使某些事情并发并不是很困难。其次最好的是拥有细粒度的锁。然而,粗粒度的锁更容易推理,因此更容易相信它们的正确性。除非你手头有一个经过良好测试的无锁类,具有适当的用途,否则请从粗粒度锁开始(使用同一个锁阻止一大堆操作的锁),然后转向更细粒度的,因为有逻辑死锁(您可以看到两个不相关的操作可能会相互阻塞)或在需要时作为优化。
  2. 永远不要锁定 this,因为您的代码外部的某些东西可能会锁定该对象,并且您很容易陷入死锁或至少只能通过查看内部代码才能发现的锁定争用到类(class)和调用代码(编写一个代码的人可能无法访问另一个代码)。出于类似的原因,永远不要锁定 Type。
  3. 出于类似的原因,只锁定私有(private)成员。这样只有类内部的代码才能锁定它,并且锁争用的可能性被限制在那个地方。
  4. 避免对此类代码使用partial,如果您确实使用了partial,请尝试将对象上的所有锁保持在同一位置。这里没有技术原因,但当您需要考虑所有可能的锁可能位置时,它确实有帮助。
  5. 永远不要锁定值类型(整数类型、结构等)。这样做会将值装箱到一个新对象并锁定它。然后,当一些其他代码试图获得锁时,它将框到另一个对象并锁定它。本质上根本没有锁定(除非理论上的优化,即使用蝇量级模式的拳击[它没有]但实际上会使事情变得更糟)。
  6. 有时锁的用途与使用锁时使用的单个对象有关,不使用锁时则不使用。在这种情况下,将此对象用作要锁定的对象有助于提高代码的可读性,将锁与该对象相关联。
  7. 拥有一个纯粹为了锁的目的而存在的对象永远不会错,所以如果您不确定,就这样做。
  8. 如果受影响的任何对象是静态的,则锁定对象必须是静态的,否则可以是实例。相反,由于实例锁比静态锁粒度更细,因此实例在适用时更好。