并行和线程安全串行模式

时间:2012-07-06 16:58:04

标签: .net thread-safety task-parallel-library

是否存在并行与线程安全计算相结合的模式?

需要计算第一步将从并行中受益的结果,第二步是并行结果的串行过程。

一个选项是运行并行并将输出保存到集合,然后连续处理集合,我有这个工作。问题在于内存管理,因为集合可能非常大。

以下是串行版本。基本上我想并行TableQueryGetRowKeys并以线程安全的方式使用该结果。试图只是平行于for并锁定最终结果,但rowKeys可能会关闭。尝试聚合,但我无法弄清楚如何将集合传递给聚合,更不用说在聚合中执行线程安全的交叉。

IEnumerable<string> finalResults = null;
if (partitionKey.Length == 0) return finalResults;
object lockObject = new object();
finalResults = TableQueryGetRowKeys(partitionKey[0], 0);
HashSet<string> rowKeys;
for(int i = 1; i < partitionKey.Length; i++)
{
    // IO operation to Azure Table Storage against the PartitionKey
    // so very amenable to parallel
    rowKeys = TableQueryGetRowKeys(partitionKey[i]);
    // a memory and CPU operation 
    // this should be much faster than TableQueryGetRowKeys
    // going parallel and wrapping this in a lock did not properly synch rowKeys
    finalResults = finalResults.Intersect(rowKeys); 
}
return finalResults;

1 个答案:

答案 0 :(得分:2)

假设TableQueryGetRowKeys是线程安全的:

var final = partitionKey.AsParallel()
                        // By returning AsParallel we can get parallel intersect
                        .Select(k => TableQueryGetRowKeys(k).AsParallel())
                        .Aggregate((x, y) => x.Intersect(y));

// Using fake-ish data I see about a 30% speed-up on a 4-core machine:
// static HashSet<string> TableQueryGetRowKeys(string prefix)
// {
//     // Simulate 1s of IO round-trip
//     if (useSleep) Thread.Sleep(1000);
//
//     return new HashSet<string>(
//         Enumerable.Range(0, 500)
//                   .Select(_ => random.Value.Next(0, 500).ToString()));
// }

这种算法以逐步的方式工作:

  1. partitionKey.AsParallel()将常规IEnumerable<string>变为ParallelQuery<string>,允许并行处理序列。
  2. 接下来,ParallelEnumerable.Select用于并行呼叫TableQueryGetRowKeys
  3. 每次拨打TableQueryGetRowKeys的结果都会使用ParallelQuery<T>包裹在AsParallel()中。
  4. ParallelEnumerable.Intersect用作TableQueryGetRowKeys返回的每个“并行启用”枚举的聚合函数。

  5. 实际上,可以使用 in serial 来删除AsParallel来取代之前的代码,如下所示:

    var serialEquivalent = partitionKey.Select(k => TableQueryGetRowKeys(k))
                                       .Aggregate((x,y) => x.Intersect(y));
    

    当您查看实施中的肉和土豆时,您可以“说服”自己这相当于您的方法:

    IEnumerable<string> results = SomeMethod(0);
    for (int ii = 1; ii < count; ++ii)
    {
        results = results.Intersect(SomeMethod(ii));
    }
    

    使用+代替Intersect重写上述内容:

    int results = SomeMethod(0);
    for (int ii = 1; ii < count; ++ii)
    {
        results = results + SomeMethod(ii);
    }
    

    现在很清楚,可以使用Intersect代替其他更“常见”的聚合函数(例如数学运算符)。