使用线程局部数据将简单算法移植到TPL

时间:2013-02-11 08:34:37

标签: c# .net task-parallel-library cluster-computing

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

1 个答案:

答案 0 :(得分:1)

我认为你的问题是对“线程本地数据”的误解。根据{{​​3}},它是:

  

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

这意味着循环的某些迭代将共享相同的Cluster对象,这将导致不正确的结果。如果localInitlocalFinally为每次迭代执行,那么它们将毫无用处,因为您可以通过将代码移动到循环的开头和结尾来完成相同的操作。

委托代表的原因是您可以将它们用于优化。使用它们,您不必经常访问共享状态(在您的情况下为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);
        }
    }
}

(在上面的代码中,我还将lockif切换为优化。)

如果您认为使用线程本地数据进行优化对您有用(即实际上会加快速度),您可以使用它。但有问题的数据必须是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);
    }
}