考虑以下函数,该函数迭代通用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<T>
,这是一种可接受的方法吗?...
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。
这是一种以线程安全方式修改集合的合理方法吗?
答案 0 :(得分:-1)
UpdateList
的并发版本不是线程安全的,因为它引入了race condition。
在多线程的情况下,您的UpdateList
的第一个版本与第二个版本不同。你明白为什么吗?如果您启动两个线程,UpdateList
执行idToFind_1
,另一个执行idToFind_2
同一个ConcurrentBag<T> Items
。然后第一个线程可能会取出第二个需要更新的项目。因此,idToFind_2
的项目很可能会错过更新,反之亦然。在这里我们有竞争条件:如果thread1及时放回项目,它将获得更新否则它将不会。
此外,您仍然需要处理这样一个事实:您正在改变从多个线程访问的项目,并且这不是安全的(Servy的评论)。
由于实施在某种程度上效率低下。您是否考虑使用另一个更合适的数据结构,并且可能通过利用任何其他代码块使用的lock来关注同步,以提供对数据结构的同一实例的独占访问。
此外,由于tempItems
是UpdateList
的本地,因此您不需要线程安全的集合,因为没有同步点。所以一个简单的List<T>
就足够了。
参数不需要ref
关键字,请参阅When to use ref and when it is not necessary in C#。