我有一个非常简单的算法,可以根据彼此的x
和y
距离来聚类blob。我使用Parallel.For
移植相同的线程本地数据,但结果不正确。换句话说,我可能没有正确使用同步来隔离每个线程。
根本无法弄清楚为什么两种实现的结果不同。任何想法都将不胜感激。
我想发布完全可编译的代码,但所使用的对象与项目上下文的集成过于紧密。由于该算法非常有用,希望这不会妨碍。
课程级别上限:
/// <summary>
/// Contains the master blobl collection to be clustered.
/// </summary>
public List<Blob> Blobs { get; private set; }
/// <summary>
/// List of clusters to be computed.
/// </summary>
public List<Cluster> Clusters { get; private set; }
线性示例(正常工作):
Cluster cluster = null;
for (int i = 0; i < this.Blobs.Count; i++)
{
cluster = new Cluster();
cluster.Id = i;
if (this.Blobs [i].ClusterId == 0)
{
cluster.Blobs.Add(this.Blobs [i], i);
for (int j = 0; j < this.Blobs.Count; j++)
{
if (this.Blobs [j].ClusterId == 0)
{
if (this.Blobs [i].Rectangle.IntersectsWith(this.Blobs [j].Rectangle))
{
cluster.Blobs.Add(this.Blobs [j], i);
}
else if (this.Blobs [i].Rectangle.IsCloseTo(this.Blobs [j].Rectangle, distanceThreshold))
{
cluster.Blobs.Add(this.Blobs [j], i);
}
}
}
}
if (cluster.Blobs.Count > 2)
{
this.Clusters.Add(cluster);
}
}
并行端口(群集不正确):
System.Threading.Tasks.Parallel.For<Cluster>
(
0,
this.Blobs.Count,
new ParallelOptions() { MaxDegreeOfParallelism = degreeOfParallelism },
() => new Cluster(),
(i, loop, cluster) =>
{
cluster.Id = i;
if (this.Blobs [i].ClusterId == 0)
{
cluster.Blobs.Add(this.Blobs [i], i);
for (int j = 0; j < this.Blobs.Count; j++)
{
if (this.Blobs [j].ClusterId == 0)
{
if (this.Blobs [i].Rectangle.IntersectsWith(this.Blobs [j].Rectangle))
{
cluster.Blobs.Add(this.Blobs [j], i);
}
else if (this.Blobs [i].Rectangle.IsCloseTo(this.Blobs [j].Rectangle, distanceThreshold))
{
cluster.Blobs.Add(this.Blobs [j], i);
}
}
}
}
return (cluster);
},
(cluster) =>
{
lock (this.Clusters)
{
if (cluster.Blobs.Count > 2)
{
this.Clusters.Add(cluster);
}
}
}
);
答案 0 :(得分:1)
我认为你的问题是对“线程本地数据”的误解。根据{{3}},它是:
[...]某些本地状态,可以在同一个线程上执行的迭代之间共享。
这意味着循环的某些迭代将共享相同的Cluster
对象,这将导致不正确的结果。如果localInit
和localFinally
为每次迭代执行,那么它们将毫无用处,因为您可以通过将代码移动到循环的开头和结尾来完成相同的操作。
委托代表的原因是您可以将它们用于优化。使用它们,您不必经常访问共享状态(在您的情况下为this.Clusters
),这可以提高性能。
如果您不需要此优化,请不要使用这两个代理,而是像这样编写循环体:
i =>
{
var cluster = new Cluster { Id = i };
// rest of the loop here
if (cluster.Blobs.Count > 2)
{
lock (this.Clusters)
{
this.Clusters.Add(cluster);
}
}
}
(在上面的代码中,我还将lock
与if
切换为优化。)
如果您认为使用线程本地数据进行优化对您有用(即实际上会加快速度),您可以使用它。但有问题的数据必须是Cluster
的列表,而不仅仅是单个Cluster
。类似的东西:
() => new List<Cluster>(),
(i, loop, clusters) =>
{
var cluster = new Cluster { Id = i };
// rest of the loop here
if (cluster.Blobs.Count > 2)
clusters.Add(cluster);
return clusters;
},
clusters =>
{
lock (this.Clusters)
{
this.Clusters.AddRange(clusters);
}
}