【问题标题】:When to lock a thread-safe collection in .net ? ( & when not to lock ? )何时在 .net 中锁定线程安全集合? (&什么时候不锁?)
【发布时间】:2015-03-17 08:31:51
【问题描述】:

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

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


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


不锁定线程安全集合会带来什么影响?

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

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

MSDN 上的评论说:

如果您在不同的线程上同时调用 GetOrAdd, addValueFactory 可能会被多次调用,但它的键/值对 可能不会为每次调用添加到字典中。

【问题讨论】:

    标签: c# .net multithreading concurrency task-parallel-library


    【解决方案1】:

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

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

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


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

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

    【讨论】:

    • 它公开的方法,它们是线程安全的吗?查看 MSDN 上的评论(请参阅更新的问题),微软似乎不保证这一点。
    【解决方案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 使用细粒度锁定来确保线程安全。 (对字典的读取操作以无锁方式执行。)但是,这些方法的委托在锁外调用,以避免在锁下执行未知代码可能出现的问题。因此,这些委托执行的代码不受操作的原子性影响。

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

    【讨论】:

    • 我已更新问题以反映我当前的情况。在阅读了你和纪尧姆的回答后,我检查了 MSDN(见有问题的评论)。现在我很担心,因为 MSDN 引用。似乎不确定。
    • @BilalFazlani 我理解您的担忧,但它似乎只影响这些方法的某些重载。更新了我的答案。
    • 我明白了,现在已经开始有意义了。通过仔细阅读,在我看来,虽然委托将在锁外执行,但委托返回的值只有在锁定后才会添加到字典中。如果这是真的,我觉得使用ConcurrentDictionary 而不锁定它是安全的。
    猜你喜欢
    • 2014-02-25
    • 1970-01-01
    • 2012-02-01
    • 2012-05-27
    • 1970-01-01
    • 2021-12-19
    • 2011-06-09
    • 2012-04-04
    • 2021-11-29
    相关资源
    最近更新 更多