【问题标题】:Acceptable method for modifying an item in a ConcurrentBag?修改 ConcurrentBag 中项目的可接受方法?
【发布时间】:2014-10-01 09:43:15
【问题描述】:

考虑以下函数,它遍历通用 List<T>: Items,并在找到匹配项时更改匹配项:

void UpdateList(ref List<clsMyClass> Items, int idToFind) {
    foreach(var Item in Items) {
        if (Item.ID == idToFind)
        {   
            // modify the item
            Item.SomeIntCounter++;
            return;
        }
    }
}

现在,如果我想做同样的事情,但这次使用线程安全的ConcurrentBag&lt;T&gt;,这是一种可接受的方法吗?...

void UpdateList(ref ConcurrentBag<clsMyClass> Items, int idToFind) {
    clsMyClass Item;
    bool found = false;
    ConcurrentBag<clsMyClass> tempItems = new ConcurrentBag<clsMyClass>();
    while(Items.Count > 0) {
        if (Items.TryTake(out Item))
        {
            if (Item.ID == idToFind)
            {
                //modify the item
                Item.SomeIntCounter++;
                found = true;
            }
            tempItems.Add(Item);
            if (found) break;
        }
    }
    foreach(var tempItem in tempItems) Items.Add(tempItem);
}

这里的想法是,从 ConcurrentBag 中删除每个项目并添加到临时项目,直到找到并更改匹配的项目,然后将所有删除的项目重新添加到 ConcurrentBag。

这是以线程安全的方式修改集合的明智方式吗?

【问题讨论】:

  • 如果您的ID 是独一无二的,我会说ConcurrentDictionary 更合适。不,您的第二种方法不是线程安全的,因为您没有使用任何锁,并且正在获取物品,创建临时包等,如果同时发生,所有这些都可能导致问题。
  • @Maarten 谢谢 - ID 并不总是唯一的,因为根据项目中使用的类,未保存的条目的 id 为 0。另外,我认为 Concurrent 命名空间使用了自己的内部锁定方法。我的函数还使用了我认为的从 ConcurrentBag 中删除项目的线程安全方法:TryTake()?
  • 如果您希望某些东西被称为 线程安全,那么 您的操作作为一个整体 必须是线程安全的,而且事实上您使用线程安全的方法来移除一个项目是不够的。
  • 即使您对集合本身的操作发生了改变,使得使用该集合的其他线程不会破坏它,您仍然必须处理您正在改变访问的项目这一事实来自多个线程,这并不安全。

标签: c# multithreading collections


【解决方案1】:

UpdateList 的并发版本不是线程安全的,因为它引入了 race condition

在多线程的情况下,您的第一版 UpdateList 不等同于您的第二版。你明白为什么吗?如果您启动两个线程执行UpdateList,一个使用idToFind_1,另一个使用idToFind_2 处理同一个ConcurrentBag&lt;T&gt; Items。然后第一个线程可能会取出第二个需要更新的项目。因此,带有idToFind_2 的项目很有可能会错过更新,反之亦然。这里我们有竞态条件:如果线程 1 及时将项目放回原处,它将获得更新否则它不会。

此外,您仍然必须处理这样一个事实,即您正在改变从多个线程访问的项目,这并不安全(Servy 的评论)。

由于实施在某种程度上是低效的。您是否考虑过使用另一种更合适的数据结构,并可能通过使用 lock 来处理同步问题,任何其他代码块都使用它来提供对同一数据结构实例的独占访问。

此外,由于tempItemsUpdateList 是本地的,因此您不需要线程安全集合,因为同步没有意义。所以一个简单的List&lt;T&gt; 也足够了。

参数不需要ref 关键字,请参阅When to use ref and when it is not necessary in C#

【讨论】:

  • OP 的代码是安全的。 lock 仅在访问此数据的所有其他代码块也锁定在同一实例上时才有用。如果没有ref 关键字,对象将不会通过引用传递。它将按值传递,但该值将是一个引用。这是一个非常重要的区别。
猜你喜欢
  • 2022-01-13
  • 2012-09-18
  • 1970-01-01
  • 2020-05-31
  • 2012-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多