在ConcurrentDictionary中实现的dictionary成员是线程安全的吗?

本文关键字:线程 安全 dictionary ConcurrentDictionary 实现 成员 | 更新日期: 2023-09-27 18:03:10

当调用DoStuffToDictionary(dictionaryTwo)时,是否可以安全地假设方法体内的操作包括索引器和LINQ扩展方法也将是线程安全的?

换句话说,会出现跨线程异常或死锁吗?

var dictionaryOne = new ConcurrentDictionary<int,int>();
var dictionaryTwo = new Dictionary<int,int>();
DoStuffToDictionary(dictionaryOne);
DoStuffToDictionary(dictionaryTwo);
void DoStuffToDictionary(IDictionary<int,int> items) {
   // Manipulate dictionary    
   if (items[0] == -1) {
     items[0] = 0; // Dumb example, but are indexers like this OK?
  }
}

在ConcurrentDictionary中实现的dictionary成员是线程安全的吗?

这段代码有几个问题:

  1. IDictionary接口可以被任何类型的字典实现
    您的示例当然不是线程安全的,因为您在IDictionary<int,int>接口上工作,这不能保证任何线程安全。甚至你的代码同时传递一个Dictionary和一个ConcurrentDictionary给方法。

  2. 事务需要是原子的,以使它们线程安全
    即使字典实现保证是线程安全的,您的代码也不会,因为您没有在两个调用:

    之间锁定对字典的访问。
    if (items[0] == -1) {
        // <-- another thread can access items in this moment
        items[0] = 0;
    }
    
  3. 返回LINQ查询永远不是线程安全的
    如果您使用LINQ从您的方法返回IEnumerableIQueriable,那么锁几乎没有影响,除非您使用ToList()方法立即计算表达式并缓存结果。这是由于LINQ只"准备"要执行的查询。如果您从一个方法返回一个IEnumerable,实际的字典将在方法结束后(因此,在锁之外)被访问

这段代码最大的问题在于,您是在传递周围的IDictionary实例,这意味着代码的其他部分可以直接访问它,并且必须非常小心地锁定同一个锁对象实例。这是痛苦的,正确实现容易出错,容易意外中断,并且难以检测(竞争条件可能在极少数情况下显示症状)。

你可以做几件事来改进代码:

  1. 不要传递IDictionary,而是传递自己的接口(首选)
    使字典成为实现一些自定义接口的类的私有成员,抽象所有操作,并使用锁来确保线程安全(或在底层使用ConcurrentDictionary)。这样你就可以确保所有的调用都被同一个锁实例所锁定。

  2. 不要使用接口,而总是传递ConcurrentDictionary
    只要使用ConcurrentDictionary提供的特定的原子方法(GetOrAdd, AddOrUpdate等),这将是线程安全的。像在示例中那样使用简单的访问方法将不是线程安全的,这意味着您仍然需要小心使用它。另一个缺点是,如果需要的话,您将无法抽象功能(不可能包装/代理字典,并且您将无法使用其他字典实现)。

  3. 传递IDictionary,并锁定字典本身(根本不推荐)。
    这是一个丑陋的黑客,不幸的是,它被使用得比它应该使用的更频繁。这意味着你需要在中访问这个字典的每个部分执行此操作,并在此过程中额外注意锁定多个操作。

   if (items[0] == -1) {
     items[0] = 0; // Dumb example, but are indexers like this OK?

不是线程安全的…使用ConcurrentDictionary this

    ConcurrentDictionary<int, int> D = new ConcurrentDictionary<int, int>();
    D.TryUpdate(0, 0, -1); // this is threadsade regarding a ConcurrentDictionary

将是线程安全的,您希望通过上述行实现的结果。

1)对于Dictionary LINQ扩展方法不是线程安全的,因为类本身不是线程安全的,但对于ConcurrentDictionary它是线程安全的,因此调用LINQ扩展函数为它是线程安全的。

编辑:

2) Is it safe to assume the operations within the method body including indexers我不知道你所说的including indexers是什么意思,但如果你是指字典不是空的。那么对于Dictionary,你不应该认为它包含项目…但是对于ConcurrentDictionary,您可以使用以下方法:AddOrUpdate();, GetOrAdd();, TryAdd();, TryGetValue();