我有一个像这样的当前算法。
public class Executor
{
private ParallelOptions options = new ParallelOptions();
private IList<Step> AllSteps;
public void Execute()
{
options.MaxDegreeOfParallelism = 4;
var rootSteps = AllSteps.Where(s => !s.Parents.Any());
Parallel.Foreach(rootSteps, options, RecursivelyExecuteStep);
}
private void RecursivelyExecuteStep(Step step)
{
ExecuteStep();
var childSteps = AllSteps.Where(s=>s.Parents.Contains(step)
&& step.Parents.All(p=>p.IsComplete);
Parallel.ForEach(childSteps, options, RecursivelyExecuteStep);
}
}
ParallelOptions.MaxDegreeOfParallelism将是一个输入变量(但为了简洁,将其排除在代码示例之外)。
我想知道是否自动为我处理线程池,或者每次都创建新线程。另外,优化这一点的最佳方式是线程池化我想要的东西。我如何使用线程池。我是多线程的新手,4.5 [.1]
中的新功能这不会将算法限制为只有4个线程,因为每个Parallel.Foreach都拥有自己的MaxDegreeOfParallelism 4,因此不会将应用程序中的所有线程限制为4吗?如何将应用程序中的所有线程限制为4?
编辑:MaxDegreeOfParallelism
答案 0 :(得分:1)
Parallel.Foreach
基本上是将工作项排队到.NET ThreadPool的好方法。
您的应用程序(进程)只有ThreadPool
的一个实例,它会尽可能地了解它使用多少并发线程,并考虑可用内核和虚拟内存的数量。
是的,.NET ThreadPool
为您处理线程池,在许多情况下您不必担心它,使用Parallel.Foreach
并让它继续使用它。
编辑:正如其他人所指出的,你应该小心过度使用ThreadPool,因为它是一个共享资源,它可能会干扰你的应用程序的其他部分。如果您的项目阻塞或长时间运行,它也将开始创建新线程,这通常是浪费。根据经验,工作项应该相对较快,最好是非阻塞的。您应该进行测试和基准测试,如果它适用于您的用例,则非常方便。
如果要进行显式控制,可以通过调用ThreadPool
来控制应用程序中ThreadPool.SetMaxThreads
使用的最大并发线程数。除非你真的需要,并且知道你在做什么,否则我建议不要这样做。例如,ThreadPool
已经尝试避免使用比核心更多的并发线程。
使用ParallellOptions.MaxDegreeOfParallelism
可以做的只是进一步限制用于执行对Parallel.Foreach
的特定调用的并发ThreadPool线程的数量。
如果您需要更加明确地控制算法调用使用的并发线程数,这里有一些可能的替代方案(可以说,增加实现复杂性):
Parellel.Foreach
时限制并发性。例如,您可以考虑仅在顶层使用Parallel.Foreach
(使用ParellelOptions.MaxDegreeOfParallelism
参数)并让RecursivelyExecuteStep
使用标准foreach。ParallelOptions.TaskScheduler
设置为QueuedTaskScheduler
的{{1}}实例,如here所述。答案 1 :(得分:1)
您可以使用TPL DataFlow
库解决此问题(您可以通过NuGet获取)。正如在其他答案中所说,Parallel
类在内部使用ThreadPool
,你不应该为此烦恼。
使用TPL数据流,您唯一需要的是创建一个TransformManyBlock<TInput,TOutput>
自身链接(或link BufferBlock
with ActionBlock
with Encapsulate
extension),并设置MaxDegreeOfParallelism
= 4或您认为应该是的任何常量
答案 2 :(得分:0)
简单直接的实现可能如下所示:
ParallelOptions Options = new ParallelOptions{MaxDegreeOfParallelism = 4};
IList<Step> AllSteps;
public void Execute()
{
var RemainingSteps = new HashSet<Step>(AllSteps);
while(RemainingSteps.Count > 0)
{
var ExecutableSteps = RemainingSteps.Where(s => s.Parents.All(p => p.IsComplete)).ToList();
Parallel.ForEach(ExecutableSteps, Options, ExecuteStep);
RemainingSteps.ExceptWith(ExecutableSteps);
}
}
当然,这将分阶段执行步骤,因此您不会总是具有最大并发性。您可能只在每个阶段结束时执行一个步骤,因为执行的后续步骤仅在当前阶段的所有步骤完成后才会实现。
如果您想提高并发性,我建议您使用BlockingCollection
。在这种情况下,您需要implement a custom partitioner对阻止集合使用Parallel.ForEach
。您还需要同时收集剩余的步骤,这样您就不会多次排队同一步(先前评论过的竞争条件)。
public class Executor
{
ParallelOptions Options = new ParallelOptions() { MaxDegreeOfParallelism = 4 };
IList<Step> AllSteps;
//concurrent hashset of remaining steps (used to prevent race conditions)
ConcurentDictionary<Step, Step> RemainingSteps = new ConcurentDictionary<Step, Step>();
//blocking collection of steps that can execute next
BlockingCollection<Step> ExecutionQueue = new BlockingCollection<Step>();
public void Execute()
{
foreach(var step in AllSteps)
{
if(step.Parents.All(p => p.IsComplete))
{
ExecutionQueue.Add(step);
}
else
{
RemainingSteps.Add(step, step);
}
}
Parallel.ForEach(
GetConsumingPartitioner(ExecutionQueue),
Options,
Execute);
}
void Execute(Step step)
{
ExecuteStep(step);
if(RemainingSteps.IsEmpty)
{
//we're done, all steps are complete
executionQueue.CompleteAdding();
return;
}
//queue up the steps that can execute next (concurrent dictionary enumeration returns a copy, so subsequent removal is safe)
foreach(var step in RemainingSteps.Values.Where(s => s.Parents.All(p => p.IsComplete)))
{
//note, removal only occurs once, so this elimiates the race condition
Step NextStep;
if(RemainingSteps.TryRemove(step, out NextStep))
{
executionQueue.Add(NextStep);
}
}
}
Partitioner<T> GetConsumingPartitioner<T>(BlockingCollection<T> collection)
{
return new BlockingCollectionPartitioner<T>(collection);
}
class BlockingCollectionPartitioner<T> : Partitioner<T>
{
readonly BlockingCollection<T> Collection;
public BlockingCollectionPartitioner(BlockingCollection<T> collection)
{
if (collection == null) throw new ArgumentNullException("collection");
Collection = collection;
}
public override bool SupportsDynamicPartitions { get { return true; } }
public override IList<IEnumerator<T>> GetPartitions(int partitionCount)
{
if (partitionCount < 1) throw new ArgumentOutOfRangeException("partitionCount");
var Enumerable = GetDynamicPartitions();
return Enumerable.Range(0, partitionCount)
.Select(i => Enumerable.GetEnumerator()).ToList();
}
public override IEnumerable<T> GetDynamicPartitions()
{
return Collection.GetConsumingEnumerable();
}
}
}