C# 尽管完成了所有迭代,但带有local的Parallel.Foreach最终还是会停止
在我的Parallel.ForEach循环中,所有线程都会调用localFinally委托。 我发现这种情况发生在我的并行循环停止时。 在我的并行循环中,我有大约三个在循环完成之前返回的条件检查阶段。看起来,当线程从这些阶段返回时,而不是整个主体的执行时,它才不会执行localFinally委托 循环结构如下所示:C# 尽管完成了所有迭代,但带有local的Parallel.Foreach最终还是会停止,c#,multithreading,task-parallel-library,C#,Multithreading,Task Parallel Library,在我的Parallel.ForEach循环中,所有线程都会调用localFinally委托。 我发现这种情况发生在我的并行循环停止时。 在我的并行循环中,我有大约三个在循环完成之前返回的条件检查阶段。看起来,当线程从这些阶段返回时,而不是整个主体的执行时,它才不会执行localFinally委托 循环结构如下所示: var startingThread = Thread.CurrentThread; Parallel.ForEach(fullList, opt, ()=&g
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最后一个线程的日志是--
调用线程状态为线程6循环Indx 16的WaitSleepJoin循环仍然无法正常完成,并且仍处于暂停状态。。。有什么线索可以解释为什么会有摊位吗
干杯 在看到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);
});
var测试=新列表();
对于(int i=0;i<1000;i++)
{
test.Add(null);
}
int finalcount=0;
int itemcount=0;
int loopcount=0;
Parallel.ForEach(test,()=>newlist(),
(项目、循环状态、索引、循环)=>
{
联锁增量(参考循环计数);
循环。添加(“a”);
//睡眠(100);
回路;
},
l=>
{
联锁。添加(参考项目计数,l计数);
联锁增量(参考最终计数);
});
在循环结束时,itemcount和loopcount与预期一样为1000,并且(在我的机器上)finalcount为1或2,具体取决于执行速度。在有以下条件的情况下:当直接返回时,执行速度可能要快得多,并且不需要额外的线程。只有在执行定位工作时,才需要更多的线程。但是,参数(在我的例子中是l)包含所有执行的组合列表。
这可能是日志差异的原因吗?我想您只是误解了
localFinally
的意思。它不是为每个项目调用的,而是为Parallel.ForEach()
使用的每个线程调用的。许多项目可以共享同一个线程
它存在的原因是,您可以在每个线程上独立地执行一些聚合,并最终将它们连接在一起。这样,您只需在一小段代码中处理同步(并使其影响您的性能)
例如,如果要计算一组项目的得分总和,可以这样做:
int totalSum = 0;
Parallel.ForEach(
collection, item => Interlocked.Add(ref totalSum, ComputeScore(item)));
int totalSum = 0;
Parallel.ForEach(
collection,
() => 0,
(item, state, localSum) => localSum + ComputeScore(item),
localSum => Interlocked.Add(ref totalSum, localSum));
但是在这里,您可以为每个项目调用Interlocked.Add()
,这可能会很慢。使用localInit
和localFinally
,您可以像这样重写代码:
int totalSum = 0;
Parallel.ForEach(
collection, item => Interlocked.Add(ref totalSum, ComputeScore(item)));
int totalSum = 0;
Parallel.ForEach(
collection,
() => 0,
(item, state, localSum) => localSum + ComputeScore(item),
localSum => Interlocked.Add(ref totalSum, localSum));
请注意,代码仅在localFinally
中使用Interlocked.Add()
,并在正文中访问全局状态。这样,同步成本只需支付几次,每使用一个线程一次
注意:我在本例中使用了联锁
,因为它非常简单,而且非常明显是正确的。如果代码更复杂,我会首先使用lock
,并尝试仅在需要良好性能时才使用Interlocked
。可能是死锁,其中可能只是伪代码,但在当前状态下将永远无法达到条件3。if(cond2)在条件周围没有括号(因此只有进程(项)在它下面)。@RobertVerpalen no这只是由于省略括号而导致的伪代码中的一个错误…只是一个简单的错误,日志机制是如何实现的?日志机制可能是问题所在吗?有一次,我花了一天的时间寻找一个问题,我的日志有点错误,告诉我这个问题存在(但没有)=p可能你只是误解了localFinally
的意思。它不是为每个项目调用的,而是为Paralle.ForEach()
使用的每个线程调用的。而且许多项可以共享同一个线程。您不应该使用interlocted.Increment
和interlocted.Add
方法来避免各种计数器可能出现的线程争用情况吗?在我的本地测试场景中,它们是不稳定的,因此它们应该是线程安全的,并且结果是相同的,但是对于发布的示例,您是对的,应该使用某种类型的锁定used@RobertVerpalen将字段标记为volatile并不能神奇地使其线程安全。特别是,这并不意味着++
将正常工作,因为++
不是原子的。嗯,然后再次显示,不稳定是不够的,只是使用线程睡眠快速检查线程数是否增加,我的循环数是987。更改代码以使用联锁功能。谢谢你的头up@svick,所以我注意到,你和克里斯是绝对正确的!从现在起我会记住这一点。为了清楚起见:结果是一样的。非常感谢你的启发性回应。。。在我的实现中,localFinally没有任何用途,只是用于调试目的,因为我的parallel.ForEach循环执行所有迭代,但仍然处于暂停状态。这是localFinally的日志输出,它在调用循环之前打印CurrentThread的状态——调用线程状态是线程6循环Indx 16的WaitSleepJoin,但是,尽管完成了所有的迭代,Parallel.ForEach并没有优雅地退出任何线索,为什么循环不会优雅地终止?非常感谢您的回答!!我设法找到了代码在其中一个调用中阻塞的点,我让循环继续到完成!干杯