代码之家  ›  专栏  ›  技术社区  ›  Paul Hadfield

为了线程安全,这是要锁定的正确对象吗?

  •  1
  • Paul Hadfield  · 技术社区  · 15 年前

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

    因为静态字典的唯一使用位置是在构造函数中,所以我觉得在访问字典的过程中锁定它是安全的。这是否会导致以后出现任何无法预见的问题,比如阻塞/死锁?我什么也看不见,但在过去,我真的没有必要过多地考虑这类事情。

    我也考虑过 锁定(此): 但我认为这不会起作用,因为它只会锁定正在创建的PerformanceCounter的实例,而不会锁定底层静态字典(线程安全也是如此)。

    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 回复  |  直到 15 年前
        1
  •  0
  •   flq    15 年前

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

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

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

        2
  •  5
  •   Jon Hanna    15 年前

    不要反对使用同谋的建议,而是更一般地回答:

    1. 最好的锁定解决方案是完全不锁定。有时,在没有锁定的情况下使某些内容并发并不十分困难。其次,最好是拥有细粒度的锁。然而,粗粒度锁更容易解释,因此对其正确性充满信心。除非您有一个经过良好测试的、具有适当用途的无锁类,否则从粗粒度锁(用相同锁阻塞一系列操作的锁)开始,然后移动到细粒度更高的锁,因为存在逻辑死锁(您可以看到两个不相关的操作是liKely相互阻碍)或根据需要进行优化。
    2. 从不锁定 this 因为代码外部的东西可以锁定对象,并且很容易出现死锁,或者至少是锁争用,这只能通过查看类内部的代码和调用代码(并且编写一个代码的人可能无法访问另一个代码)才能找到。出于类似的原因,永远不要锁定 Type .
    3. 出于类似的原因,只能锁定私有成员。这样,只有类内部的代码才能锁定它,锁争用的可能性仅限于该位置。
    4. 避免使用 partial 如果你确实使用 部分 ,尝试将对象上的所有锁保持在同一位置。这里没有技术上的原因,但是当你需要考虑所有可能的地方锁可以采取帮助。
    5. 从不锁定值类型(整数类型、结构等)。这样做将把值框到一个新对象并锁定它。然后,当其他代码试图获取锁时,它将框到另一个对象并锁定该对象。本质上根本没有锁(除非理论上的优化,拳击使用飞锤模式[没有],但实际上会使情况更糟)。
    6. 有时,锁的用途与使用锁时使用的单个对象有关,而在不使用锁时不使用。在这种情况下,将此对象用作锁定对象有助于代码的可读性,从而将锁与该对象关联起来。
    7. 拥有一个纯粹为了锁的目的而存在的对象永远不会是错误的,所以如果你不确定,就这么做吧。
    8. 如果受影响的任何对象是静态的,则锁定对象必须是静态的,否则可能是实例。相反,由于实例锁比静态锁更细粒度,因此实例在适用时更好。
        3
  •  2
  •   Jakub Konecki    15 年前

    是的,它可以工作,因为容器是静态的。

    你可能想看看 ReaderWriterLockSlim 同样,你也希望如此多地阻止Theard(提高性能)

        4
  •  0
  •   x0n    15 年前

    是的,容器理论上是可以的,因为它的作用域与字典实例相同,也就是说,它是一个静态的(显然,它是字典实例)。

    然而,一旦你担心你的整体设计是你说这是在一个网站托管。每个ASP.NET工作进程都是单独的进程,因此当IIS因空闲或其他原因回收工作进程时,静态字典可能会被销毁。此外,如果您使用的是Web花园(每个应用程序有多个工作人员),则可能有多个字典实例。