在下面的简化代码中,我生成了200个任务。每个任务都需要经过一个由锁保护的关键区域。锁内部是一个.AsParallel()语句。当我运行程序时,没有任何反应。该程序无限期挂起,没有打印任何内容。
private static object lockObject = new object();
static void Main(string[] args)
{
RunTasks();
}
private static void RunTasks()
{
List<Task> tasks = new List<Task>();
for (int i = 0; i < 200; i++)
{
tasks.Add(Task.Factory.StartNew(PerformComputations));
}
Task.WaitAll(tasks.ToArray());
}
private static void PerformComputations()
{
// Computations
lock (lockObject)
{
// The actual operations performed here are irrelevant. The key is that they use .AsParallel()
foreach (int i in Enumerable.Range(0, 500).AsParallel().Select(i => i))
{
Console.WriteLine(i);
}
}
// Additional computations
}
但是,如果像这样实现RunTasks,一切都会正常运行(但速度很慢):
Parallel.For(0, 200, i =>
{
PerformComputations();
});
如果我从PerformComputations中删除.AsParallel()语句,那么一切也都有效。
问题:
答案 0 :(得分:0)
您对问题#1和#2的回答绝对正确。
回答#3:您可以在创建任务时指定TaskCreationOptions.LongRunning。根据{{3}}的文档,这将为任务调度程序提供提示,该任务可能需要一个额外的线程,这样它就不会阻止其他线程或工作的前进本地线程池队列中的项目。
实际上,这会使任务系统忽略ThreadPool,只为您的任务提供一个新的专用线程。
答案 1 :(得分:0)
Parallel.For和.ForEach方法以及System.Collection.Concurrent命名空间确实让您的生活更容易处理这类问题。调度程序根据进程优先级,系统工作负载,内核数量等处理您的线程管理...并行性变得简单:
static void Main(string[] args)
{
RunTasks();
}
// This sets up the parallel scheduler to use UP TO 16 simultaneous threads. In reality the thread
// workload is managed by the CLR according to how many logical threads you have available on your
// processor.
private static readonly ParallelOptions _po = new ParallelOptions() { MaxDegreeOfParallelism = 16 };
private static void RunTasks()
{
// Run 200 instances of PerformComputations in parallel.
Parallel.For(0, 200, _po, i => PerformComputations());
}
private static void PerformComputations()
{
// If you want to run the 500 iterations in parallel (sequence is not important),
// use a concurrent collection. This needs absolutely no lock, the collection is
// partitioned internally to avoid having to lock. Same goes if you need to share
// data between multiple runs of PerformComputations(), declare a static bag at
// class level.
var theBag = new ConcurrentBag<int>(Enumerable.Range(0, 500));
Parallel.ForEach(theBag, _po, i =>
{
Console.WriteLine(i.ToString());
});
// Otherwise you don't need a lock at all anyway since each element here is treated
// one at a time in sequence.
var theList = Enumerable.Range(0, 500).ToList();
foreach (var i in theList)
{
Console.WriteLine(i.ToString());
}
}
答案 2 :(得分:0)
是的,你是对的 - 原始代码锁定在PerformComputations
的并行部分。
LongRunning
强制创建一个全新的非线程池线程(告诉调度程序为该任务创建一个新线程)。注意:您可能会创建许多线程,从而导致内存开销和切换开销等问题。
private static void RunTasks()
{
List<Task> tasks = new List<Task>();
for (int i = 0; i < maxLoops; i++)
{
tasks.Add(Task.Factory.StartNew(PerformComputations, TaskCreationOptions.LongRunning));
}
Task.WaitAll(tasks.ToArray());
}
有趣的阅读:Parallelism in .NET
回答问题3:如果你不介意创建多个线程(使用Parallel.For
)vs组合结果(AsParallel().Select
)。
private static void PerformComputations()
{
lock (lockObject)
{
Parallel.For(0, 500, i =>
{
Console.WriteLine(i);
});
}
}
答案 3 :(得分:0)
首先,我不明白您为什么要使用AsParallel()
。如果您有200个大多数独立的Task
,那么它应足以充分利用您的CPU。这尤其令人困惑,因为AsParallel()
并行执行的唯一操作是无用的Select()
。
现在,要真正回答你的问题:
我最好的猜测是,RunTasks会产生200个任务,这超过了我机器上的物理核心数量。
核心数量不相关。可用线程数更重要。 TPL使用ThreadPool
,它限制了每秒创建的线程数,也是线程总数的硬限制。如果达到第一个限制,您的代码可能会变慢为爬行(并且显示不执行任何操作)。如果达到第二个限制,您的代码实际上会死锁并停止工作。
第一个限制不可配置或记录良好,the second limit is。
在任何情况下,达到这些限制中的任何一个都表明您的代码在并行性方面设计糟糕。
为什么修改后的RunTasks版本有效?它只是Parallel.For队列小于最大活动任务数吗?
是的,Parallel.For
使用较少数量的Task
,因为效率更高。
有没有办法以这样的方式编写PerformComputations,它可以使用原始的RunTasks方法,但仍然可以并行运行?
我不明白你为什么要那样做。就像我之前说的那样,我不认为并行运行Select()
是有道理的。