1

我有一个非常简单的算法,可以根据它们xy彼此的距离对 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);
            }
        }
    }
);
4

1 回答 1

1

我认为您的问题是对“线程本地数据”的误解。根据的文档Parallel.For(),它是:

[...] 一些本地状态,可能在同一线程上执行的迭代之间共享。

这意味着您的循环的某些迭代将共享同一个Cluster对象,这将导致您的结果不正确。如果每次迭代都执行localInitand 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优化。)

如果您认为使用线程本地数据的优化对您有用(即它实际上会加快速度),您可以使用它。但是有问题的数据必须是一个Clusters 的列表,而不仅仅是一个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);
    }
}
于 2013-02-11T10:38:42.687 回答