尽管完成了所有迭代,但localFinally的Parallel.Foreach仍然停滞不前

时间:2012-06-07 10:50:21

标签: c# multithreading task-parallel-library

在My Parallel.ForEach循环中,对所有线程调用localFinally委托。 我发现这是因为我的并行循环失速。 在我的并行循环中,我有大约三个条件检查阶段在循环完成之前返回。而且似乎是从这些阶段返回Threads而不是整个body的执行它不执行localFinally委托。

循环结构如下:

 var startingThread = Thread.CurrentThread;
 Parallel.ForEach(fullList, opt,
         ()=> new MultipleValues(),
         (item, loopState, index, loop) =>
         {
            if (cond 1)
                return loop;
            if (cond 2)
                {
                process(item);
                return loop;
                }
            if (cond 3)
                return loop;

            Do Work(item);
            return loop;
          },
          partial =>
           {
              Log State of startingThread and threads
            } );

我在一个小数据集上运行循环并详细记录,发现当Parallel.ForEach完成所有迭代并且localFinally的最后一个线程的Log是 - 对于线程6循环Indx 16,调用线程状态为WaitSleepJoin 循环仍然没有优雅地完成并且仍然停滞......为什么这些摊位的线索?

干杯!

2 个答案:

答案 0 :(得分:1)

在看到localFinally的定义(在每个线程完成后执行)之后做了一个快速测试运行,这让我怀疑这可能意味着并行性创建的线程比执行的循环少得多。 e.g。

        var test = new List<List<string>> ();
        for (int i = 0; i < 1000; i++)
        {
            test.Add(null);
        }

        int finalcount = 0;
        int itemcount = 0;
        int loopcount = 0;

        Parallel.ForEach(test, () => new List<string>(),
            (item, loopState, index, loop) =>
            {
                Interlocked.Increment(ref loopcount);
                loop.Add("a");
                //Thread.Sleep(100);
                return loop;
            },
            l =>
            {
                Interlocked.Add(ref itemcount, l.Count);                    
                Interlocked.Increment(ref finalcount);                    
            });

在此循环结束时,itemcount和loopcount按预期为1000,并且(在我的机器上)finalcount为1或2,具体取决于执行速度。在具有条件的情况下:当直接返回时,执行可能更快并且不需要额外的线程。只有在执行dowork时才需要更多的线程。但是参数(在我的情况下为l)包含所有执行的组合列表。 这可能是记录差异的原因吗?

答案 1 :(得分:1)

我认为你误解了localFinally的含义。它不是针对每个项目调用的,而是为Parallel.ForEach()使用的每个线程调用它。许多项目可以共享同一个帖子。

它存在的原因是你可以在每个线程上独立执行一些聚合,并且最后只将它们连接在一起。这样,您只需要在一小段代码中处理同步(并使其影响性能)。

例如,如果您想计算一组项目的得分总和,您可以这样做:

int totalSum = 0;
Parallel.ForEach(
    collection, item => Interlocked.Add(ref totalSum, ComputeScore(item)));

但是在这里,你为每个项目调用Interlocked.Add(),这可能很慢。使用localInitlocalFinally,您可以像这样重写代码:

int totalSum = 0;
Parallel.ForEach(
    collection,
    () => 0,
    (item, state, localSum) => localSum + ComputeScore(item),
    localSum => Interlocked.Add(ref totalSum, localSum));

请注意,代码仅在Interlocked.Add()中使用localFinally,并且访问body中的全局状态。这样,同步成本只需支付几次,每次使用的线程一次。

注意:我在这个例子中使用了Interlocked,因为它非常简单且非常明显正确。如果代码更复杂,我会首先使用lock,并且只有在有必要获得良好性能时才尝试使用Interlocked