【发布时间】:2014-12-29 07:34:07
【问题描述】:
我手头有这样一个场景(使用 C#):我需要在对象列表上使用并行“foreach”:此列表中的每个对象都像数据源一样工作,它正在生成一系列二进制向量模式(如“0010100110”)。在生成每个向量模式时,我需要更新共享 ConcurrentDictionary 上当前向量模式的出现次数。这个 ConcurrentDictionary 就像所有数据源中特定二进制模式的直方图。在伪代码中它应该像这样工作:
ConcurrentDictionary<BinaryPattern,int> concDict = new ConcurrentDictionary<BinaryPattern,int>();
Parallel.Foreach(var dataSource in listOfDataSources)
{
for(int i=0;i<dataSource.OperationCount;i++)
{
BinaryPattern pattern = dataSource.GeneratePattern(i);
//Add the pattern to concDict if it does not exist,
//or increment the current value of it, in a thread-safe fashion among all
//dataSource objects in parallel steps.
}
}
我在文档中阅读了 ConcurrentDictionary 类的 TryAdd() 和 TryUpdate() 方法,但我不确定我是否清楚地理解了它们。 TryAdd() 获取当前线程对 Dictionary 的访问权,并查找特定键是否存在,在这种情况下为二进制模式,然后如果不存在,则创建其条目,将其值设置为 1是这种模式的第一次出现。 TryUpdate() 获得对当前线程字典的访问权,查看具有指定键的条目的当前值是否等于“已知”值,如果是,则更新它。顺便说一句,TryGetValue() 检查字典中是否存在某个键,如果存在则返回当前值。
现在我想到以下用法,想知道它是否是 ConcurrentDictionary 的线程安全填充的正确实现:
ConcurrentDictionary<BinaryPattern,int> concDict = new ConcurrentDictionary<BinaryPattern,int>();
Parallel.Foreach(var dataSource in listOfDataSources)
{
for(int i=0;i<dataSource.OperationCount;i++)
{
BinaryPattern pattern = dataSource.GeneratePattern(i);
while(true)
{
//Look whether the pattern is in dictionary currently,
//if it is, get its current value.
int currOccurenceOfPattern;
bool isPatternInDict = concDict.TryGetValue(pattern,out currOccurenceOfPattern);
//Not in dict, try to add.
if(!isPatternInDict)
{
//If the pattern is not added in the meanwhile, add it to the dict.
//If added, then exit from the while loop.
//If not added, then skip this step and try updating again.
if(TryAdd(pattern,1))
break;
}
//The pattern is already in the dictionary.
//Try to increment its current occurrence value instead.
else
{
//If the pattern's occurence value is not incremented by another thread
//in the meanwhile, update it. If this succeeds, then exit from the loop.
//If TryUpdate fails, then we see that the value has been updated
//by another thread in the meanwhile, we need to try our chances in the next
//step of the while loop.
int newValue = currOccurenceOfPattern + 1;
if(TryUpdate(pattern,newValue,currOccurenceOfPattern))
break;
}
}
}
}
我试图将我的逻辑牢牢地总结在 cmets 中的上述代码 sn-p 中。根据我从文档中收集到的信息,考虑到 ConcurrentDictionary 的原子“TryXXX()”方法,可以以这种方式对线程安全的更新方案进行编码。这是解决问题的正确方法吗?如果不是,如何改进或更正?
【问题讨论】:
-
这个问题似乎是题外话,因为它应该在Code Review
-
我也要求正确的用法。这不是一个简单的代码审查问题。
-
当你有一个
while(true)时,你必须非常确定循环最终会结束的情况。在这里很难说,但似乎在某些情况下它可能会永远循环。 -
实际上,只要没有更新字典,线程就会在那个“while”中循环。在最坏的情况下,它永远不会更新它,直到所有其他线程完成它们的工作并完全退出,然后当前线程最终可以获得更新字典的机会,因为它成为最后一个仍然存在的线程。
标签: c# .net multithreading dictionary task-parallel-library