如何在Parallel.ForEach循环中临时同步?
c#
所以这是一个有效但效率低下的代码的最小版本:
Parallel.ForEach(list, x =>
{
doThing1(x);
});
Thing1Done = true;
Parallel.ForEach(list, x =>
{
doThing2(x);
});
Thing2Done = true;
Parallel.ForEach(list, x =>
{
doThing3(x);
});
Thing3Done = true;
直观地说,我想在同一个循环中运行所有 3 个“事物”,但它们必须能够临时同步以更新各自的Thing*n*Done属性。
这个想法的伪代码如下:
Parallel.ForEach(list, x =>
{
doThing1(x);
// wait for doThing1 to be completed for all other elements in list
Thing1Done = true;
doThing2(x);
// wait for doThing2 to be completed for all other elements in list
Thing2Done = true;
doThing3(x);
// wait for doThing3 to be completed for all other elements in list
Thing3Done = true;
});
因此,例如,有必要doThing1()为listbefore 的每个成员完成其执行Thing1Done设置为 true。doThing2()只能在Thing1Done设置后开始。
每个单独的步骤都不太昂贵,我担心天真的方法所涉及的开销。假设初始化线程所涉及的开销是我最关心的问题,那么有效解决此任务的最佳方法是什么?如果可能的话,我还想避免忙等待(线程在 while 循环中旋转,直到某些标志设置为 true 才做任何有用的事情)。
如果 Parallel 库不能做我想做的事,我愿意写一些更一般的东西。
回答
您可以使用Barrier线程同步原语:
var barrier = new Barrier(list.Count);
var options = new ParallelOptions()
{
MaxDegreeOfParallelism = list.Count,
TaskScheduler = new ThreadPerTask()
};
Parallel.ForEach(list, options, x =>
{
doThing1(x);
// wait for doThing1 to be completed for all other elements in list
barrier.SignalAndWait();
doThing2(x);
// wait for doThing2 to be completed for all other elements in list
barrier.SignalAndWait();
doThing3(x);
// wait for doThing3 to be completed for all other elements in list
barrier.SignalAndWait();
});
该SignalAndWait方法发出项目完成的信号,并等待所有项目完成(直到ParticipantsRemaining属性变为零)。在该CurrentPhaseNumber属性增加之后,所有线程同时解除阻塞,并且可以自由地向下一个里程碑竞赛。
这是处理 的项目的一种极其低效的方式list,因为它需要每个项目有一个专用线程。您将需要一个自定义TaskScheduler来实现此设置,如下所示:
public class ThreadPerTask : TaskScheduler
{
protected override void QueueTask(Task task)
{
new Thread(() => this.TryExecuteTask(task))
{
IsBackground = true
}.Start();
}
protected override bool TryExecuteTaskInline(Task task,
bool taskWasPreviouslyQueued) => false;
protected override IEnumerable<Task> GetScheduledTasks() { yield break; }
}
恕我直言,您的第一种方法,即使用多个连续Parallel.ForEach循环的方法,是解决此问题的正确方法。