好的,我已阅读Thread safe collections in .NET和Why lock Thread safe collections?。
以前的问题是以java为中心,没有回答我的问题,后面的问题的答案告诉我不需要锁定集合,因为它们应该是线程安全的。 (这就是我的想法)
现在回答我的问题, 我看到很多开发人员(在github和我的组织中)已经开始使用新的线程安全集合。但是,通常他们不会在读取和放大器周围移除锁定。写操作。 我不明白这一点。不是一个线程安全的集合......好吧,线程安全完全?
不锁定线程安全集合会带来什么影响?
编辑: PS:这是我的情况,
我有很多课程,其中一些课程有一个属性。我经常需要检查给定类型是否具有该属性(当然使用反射)。这在性能上可能很昂贵。因此决定使用ConcurrentDictionary<string,bool>
创建缓存。 string是typeName,bool指定它是否具有该属性。首先,缓存是空的,计划是在需要时继续添加缓存。我遇到ConcurrentDictionary
的{{3}}方法。我的问题是一样的,如果我不应该锁定这个方法吗?
MSDN上的评论说:
如果您在不同的线程上同时调用GetOrAdd, addValueFactory可以被多次调用,但是它的键/值对 每次通话时, 可能无法添加 到字典中。
答案 0 :(得分:4)
您不应该锁定线程安全集合,它会公开更新已锁定集合的方法,并按预期使用它们。
如果要在集合上打开枚举器时阻止修改,则线程安全集合可能与您的需求不匹配(提供的线程安全集合允许修改)。如果是这样的话,您最好使用常规集合并将其锁定在任何地方。线程安全集合的内部锁定不公开。
很难回答关于不锁定线程安全集合的含义。您不需要锁定线程安全的集合,但您可能必须锁定执行多项操作的代码。很难说没看到代码。
是的,该方法是线程安全的,但是如果同时为同一个键点击Add,它可能会多次调用AddValueFactory。最后,只会添加其中一个值,其他值将被丢弃。这可能不是一个问题...你必须检查你达到这种情况的频率,但我认为这并不常见,你可以忍受在可能永远不会发生的边缘情况下的性能损失
您还可以在静态ctor中或在需要之前构建您的词典。通过这种方式,词典填写一次,你就不会写它。这本字典只读,你不需要任何锁定,也不需要线程安全集合。
答案 1 :(得分:2)
类的方法通常会将对象从状态A更改为状态B.但是,另一个线程也可能在执行该方法期间更改对象的状态,从而可能将对象保留在一个不稳定的状态。
例如,列表可能想要在添加新项目之前检查其基础数据缓冲区是否足够大:
void Add(object item)
{
int requiredSpace = Count + 1;
if (buffer.Length < requiredSpace)
{
// increase underlying buffer
}
buffer[Count] = item;
}
现在,如果列表只有一个项目的缓冲区空间,并且两个线程同时尝试添加项目,则它们可能都认为不需要额外的缓冲区空间,可能导致IndexOutOfRangeException
开启其中一个主题。
线程安全类确保不会发生这种情况。
这并不意味着使用线程安全类会使您的代码成为线程安全的:
int count = myConcurrentCollection.Count;
myCurrentCollection.Add(item);
count++;
if (myConcurrentCollection.Count != count)
{
// some other thread has added or removed an item
}
因此,虽然集合是线程安全的,但您仍需要考虑自己代码的线程安全性。 Guillaume提到的枚举器示例是可能发生线程问题的完美示例。
关于您的评论,ConcurrentDictionary
的文档提及:
所有这些操作都是原子的,并且对于ConcurrentDictionary类的所有其他操作都是线程安全的。唯一的例外是接受委托的方法,即AddOrUpdate和GetOrAdd。对于字典的修改和写入操作,ConcurrentDictionary使用细粒度锁定来确保线程安全。 (对字典的读取操作是以无锁方式执行的。)但是,这些方法的委托在锁外部调用,以避免在锁定下执行未知代码时可能出现的问题。因此,这些代理执行的代码不受操作原子性的影响。
所以,这些重载(带代理)是例外。