何时锁定 .net 中的线程安全集合? (&什么时候不锁定?)

When to lock a thread-safe collection in .net ? ( & when not to lock ? )

好的,我已经阅读了Thread safe collections in .NET and Why lock Thread safe collections?

前一个问题以 java 为中心,没有回答我的问题,而后一个问题的答案告诉我不需要锁定集合,因为它们应该是线程安全的。 (这就是我的想法)


现在回答我的问题, 我看到很多开发人员(在 github 和我的组织中)已经开始使用新的线程安全集合。但是,他们通常不会解除对读写操作的锁定。 我不明白这一点。不是线程安全的集合吗……好吧,线程安全完全 ?


不锁定线程安全集合会产生什么影响?

编辑: PS:这是我的情况,

我有很多类,其中一些有属性。我经常需要检查给定类型是否具有该属性(当然使用反射)。这在性能上可能很昂贵。所以决定使用ConcurrentDictionary<string,bool>创建一个缓存。 string 是 typeName 和 bool 指定它是否具有属性。起初,缓存是空的,计划是在需要时继续添加。我遇到了 ConcurrentDictionaryGetOrAdd() 方法。我的问题是差不多的,如果我应该在没有锁定的情况下调用这个方法?

MSDN 上的评论说:

If you call GetOrAdd simultaneously on different threads, addValueFactory may be called multiple times, but its key/value pair might not be added to the dictionary for every call.

你不应该锁定一个线程安全的集合,它公开了更新已经锁定的集合的方法,按预期使用它们。

线程安全集合可能不符合您的需求,例如,如果您想在集合上打开枚举器时防止修改(提供的线程安全集合允许修改)。如果是这种情况,您最好使用常规集合并将其锁定在任何地方。线程安全集合的内部锁不公开。

很难回答不锁定线程安全集合的含义。您不需要锁定线程安全的集合,但您可能必须锁定执行多项操作的代码。不看代码很难说。


是的,该方法是线程安全的,但如果您同时为同一键点击添加,它可能会多次调用 AddValueFactory。最后只会添加一个值,其他值将被丢弃。这可能不是问题……您必须检查遇到这种情况的频率,但我认为这并不常见,您可以忍受在可能永远不会发生的边缘情况下的性能损失。

您还可以在静态构造函数中或在需要之前构建您的词典。这样,字典就填满了一次,你永远不会写信给它。然后字典是只读的,你不需要任何锁,也不需要线程安全集合。

class 的方法通常会将对象从状态 A 更改为状态 B。但是,另一个线程也可能会在 执行期间更改对象的状态 该方法的一部分,可能会使对象处于不稳定状态。

例如,列表可能希望在添加新项目之前检查其底层数据缓冲区是否足够大:

void Add(object item)
{
    int requiredSpace = Count + 1;
    if (buffer.Length < requiredSpace)
    {
        // increase underlying buffer
    }

    buffer[Count] = item;
}

现在,如果一个列表的缓冲区 space 仅用于再添加一个项目,并且两个线程试图同时添加一个项目,它们可能都决定不需要额外的缓冲区 space ,可能会导致这些线程之一出现 IndexOutOfRangeException

线程安全classes 确保不会发生这种情况。

这并不意味着使用线程安全的 class 会使您的代码线程安全:

int count = myConcurrentCollection.Count;
myCurrentCollection.Add(item);
count++;
if (myConcurrentCollection.Count != count)
{
    // some other thread has added or removed an item
}

所以虽然集合是线程安全的,但是你自己的代码还是要考虑线程安全的。 Guillaume 提到的枚举器示例是可能发生线程问题的完美示例。

关于您的评论,ConcurrentDictionary 的文档提到:

All these operations are atomic and are thread-safe with regards to all other operations on the ConcurrentDictionary class. The only exceptions are the methods that accept a delegate, that is, AddOrUpdate and GetOrAdd. For modifications and write operations to the dictionary, ConcurrentDictionary uses fine-grained locking to ensure thread safety. (Read operations on the dictionary are performed in a lock-free manner.) However, delegates for these methods are called outside the locks to avoid the problems that can arise from executing unknown code under a lock. Therefore, the code executed by these delegates is not subject to the atomicity of the operation.

所以是的,这些重载(接受委托)是例外。