何时锁定 .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 指定它是否具有属性。起初,缓存是空的,计划是在需要时继续添加。我遇到了 ConcurrentDictionary
的 GetOrAdd()
方法。我的问题是差不多的,如果我应该在没有锁定的情况下调用这个方法?
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.
所以是的,这些重载(接受委托)是例外。
好的,我已经阅读了Thread safe collections in .NET and Why lock Thread safe collections?。
前一个问题以 java 为中心,没有回答我的问题,而后一个问题的答案告诉我不需要锁定集合,因为它们应该是线程安全的。 (这就是我的想法)
现在回答我的问题, 我看到很多开发人员(在 github 和我的组织中)已经开始使用新的线程安全集合。但是,他们通常不会解除对读写操作的锁定。 我不明白这一点。不是线程安全的集合吗……好吧,线程安全完全 ?
不锁定线程安全集合会产生什么影响?
编辑: PS:这是我的情况,
我有很多类,其中一些有属性。我经常需要检查给定类型是否具有该属性(当然使用反射)。这在性能上可能很昂贵。所以决定使用ConcurrentDictionary<string,bool>
创建一个缓存。 string 是 typeName 和 bool 指定它是否具有属性。起初,缓存是空的,计划是在需要时继续添加。我遇到了 ConcurrentDictionary
的 GetOrAdd()
方法。我的问题是差不多的,如果我应该在没有锁定的情况下调用这个方法?
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.
所以是的,这些重载(接受委托)是例外。