我应该使用ThreadPools还是Task Parallel Library进行IO绑定操作


78

在我的一个有点像聚合器的项目中,我从网络上解析提要,播客等。

如果我使用顺序方法,那么考虑到大量的资源,要花所有的时间来处理所有这些资源(由于网络问题和类似的东西);

foreach(feed in feeds)
{
   read_from_web(feed)
   parse(feed)
}

因此,我想实现并发性,无法决定是否应该基本上使用ThreadPools处理工作线程,还是仅依靠TPL对其进行排序。

ThreadPools肯定会通过辅助线程为我完成工作,并且我会得到期望的结果(在多核CPU环境中,其他内核也将被利用)。

并发

但是我仍然想考虑TPL,因为它是推荐方法,但我对此有些担心。首先,我知道TPL使用ThreadPools,但增加了决策层。我最担心的是存在单核环境的情况。如果我没记错的话,TPL开头的工作线程数等于可用的CPU核心数。我确实担心TPL对于受IO约束的案例会产生与顺序方法相似的结果。

因此,对于IO绑定操作(以我为例,它是从Web上读取资源),最好是使用ThreadPools来控制事物,还是最好仅依赖TPL?TPL也可以用于IO绑定方案吗?

更新:我主要担心的是-在单核CPU环境中,TPL会像顺序方法一样工作还是会提供并发性?我已经在阅读《使用Microsoft .NET进行并行编程》,因此这本书却找不到确切的答案。

注意:这是我以前的问题的重新措辞[可以同时使用线程并发和并行性吗?]这句话是错误的。

Answers:


107

因此,我决定为此编写测试,并在实际数据上查看它。

测试图例

  • Itr:迭代
  • 序列:顺序方法。
  • PrlEx:并行扩展-Parallel.ForEach
  • TPL:任务并行库
  • TPool:线程池

测试结果

单核CPU [Win7-32​​]-在VMWare下运行-

Test Environment: 1 physical cpus, 1 cores, 1 logical cpus.
Will be parsing a total of 10 feeds.
________________________________________________________________________________

Itr.    Seq.    PrlEx   TPL     TPool
________________________________________________________________________________

#1      10.82s  04.05s  02.69s  02.60s
#2      07.48s  03.18s  03.17s  02.91s
#3      07.66s  03.21s  01.90s  01.68s
#4      07.43s  01.65s  01.70s  01.76s
#5      07.81s  02.20s  01.75s  01.71s
#6      07.67s  03.25s  01.97s  01.63s
#7      08.14s  01.77s  01.72s  02.66s
#8      08.04s  03.01s  02.03s  01.75s
#9      08.80s  01.71s  01.67s  01.75s
#10     10.19s  02.23s  01.62s  01.74s
________________________________________________________________________________

Avg.    08.40s  02.63s  02.02s  02.02s
________________________________________________________________________________

单核CPU [WinXP]-在VMWare下运行-

Test Environment: 1 physical cpus, NotSupported cores, NotSupported logical cpus.
Will be parsing a total of 10 feeds.
________________________________________________________________________________

Itr.    Seq.    PrlEx   TPL     TPool
________________________________________________________________________________

#1      10.79s  04.05s  02.75s  02.13s
#2      07.53s  02.84s  02.08s  02.07s
#3      07.79s  03.74s  02.04s  02.07s
#4      08.28s  02.88s  02.73s  03.43s
#5      07.55s  02.59s  03.99s  03.19s
#6      07.50s  02.90s  02.83s  02.29s
#7      07.80s  04.32s  02.78s  02.67s
#8      07.65s  03.10s  02.07s  02.53s
#9      10.70s  02.61s  02.04s  02.10s
#10     08.98s  02.88s  02.09s  02.16s
________________________________________________________________________________

Avg.    08.46s  03.19s  02.54s  02.46s
________________________________________________________________________________

双核CPU [Win7-64]

Test Environment: 1 physical cpus, 2 cores, 2 logical cpus.
Will be parsing a total of 10 feeds.
________________________________________________________________________________

Itr.    Seq.    PrlEx   TPL     TPool
________________________________________________________________________________

#1      07.09s  02.28s  02.64s  01.79s
#2      06.04s  02.53s  01.96s  01.94s
#3      05.84s  02.18s  02.08s  02.34s
#4      06.00s  01.43s  01.69s  01.43s
#5      05.74s  01.61s  01.36s  01.49s
#6      05.92s  01.59s  01.73s  01.50s
#7      06.09s  01.44s  02.14s  02.37s
#8      06.37s  01.34s  01.46s  01.36s
#9      06.57s  01.30s  01.58s  01.67s
#10     06.06s  01.95s  02.88s  01.62s
________________________________________________________________________________

Avg.    06.17s  01.76s  01.95s  01.75s
________________________________________________________________________________

四核CPU [Win7-64]-支持HyprerThreading-

Test Environment: 1 physical cpus, 4 cores, 8 logical cpus.
Will be parsing a total of 10 feeds.
________________________________________________________________________________

Itr.    Seq.    PrlEx   TPL     TPool
________________________________________________________________________________

#1      10.56s  02.03s  01.71s  01.69s
#2      07.42s  01.63s  01.71s  01.69s
#3      11.66s  01.69s  01.73s  01.61s
#4      07.52s  01.77s  01.63s  01.65s
#5      07.69s  02.32s  01.67s  01.62s
#6      07.31s  01.64s  01.53s  02.17s
#7      07.44s  02.56s  02.35s  02.31s
#8      08.36s  01.93s  01.73s  01.66s
#9      07.92s  02.15s  01.72s  01.65s
#10     07.60s  02.14s  01.68s  01.68s
________________________________________________________________________________

Avg.    08.35s  01.99s  01.75s  01.77s
________________________________________________________________________________

总结

  • 无论您是在单核环境还是多核环境下运行,Parallel Extensions,TPL和ThreadPool的行为都相同,并给出近似结果
  • TPL仍然具有优势,例如易于处理异常,取消支持以及轻松返回Task结果的能力。尽管并行扩展也是另一个可行的选择。

自行运行测试

您可以在此处下载源代码并自行运行。如果可以发布结果,我也会添加它们。

更新:修复了源链接。


6
+1使用科学的方法解决您的问题。如果我没记错的话,以上所有技术都使用线程池,这说明了类似的结果。通常,我喜欢使用Parallel Extensions,因为语法非常简单而且很懒。
Jacobs Data Solutions


1
感谢您完成所有工作以进行测试。
GregoryBrad 2014年

15

如果要最大程度地提高IO绑定任务的吞吐量,则必须将传统的异步处理模型(APM)API与基于TPL的工作结合使用。APM API是在异步IO回调挂起时取消阻塞CPU线程的唯一方法。TPL提供TaskFactory::FromAsync辅助方法,以帮助组合APM和TPL代码。

请查看MSDN上的.NET SDK的本节,标题为TPL和Traditional .NET异步编程,以获取有关如何组合这两种编程模型以实现异步必杀技的更多信息。


2

没错,TPL确实会删除您创建自己的线程池时所拥有的某些控件。但这仅在您不想深入研究时才是正确的。TPL确实允许您创建长时间运行的任务,这些任务不属于TPL线程池,并且可以很好地满足您的目的。已出版的本书是免费阅读的Microsoft .NET并行编程,将为您提供更多有关如何使用TPL的见解。您始终可以选择给Paralle.For,Tasks显式 参数分配多少线程。除此之外,如果您想完全控制,则可以用自己的TPL调度程序替换TPL调度程序。


1

您可以将自己的任务计划程序分配给TPL任务。不过,默认的工作是偷机


我已经阅读了该资源,实际上我因此而感到担忧;在.NET Framework 4中,默认任务计划程序与线程池紧密集成。如果使用默认任务计划程序,则执行并行任务的工作线程由.NET ThreadPool类管理。通常,计算机上的工作线程至少与内核一样多。-如果我在单核环境上运行该怎么办?我找不到任何澄清就可以了..
HuseyinUslu

1
因此,在这种情况下,您想强制执行最小并行度,例如最近的X度?TaskCreationOptions.LongRunning 可能就是您想要的。如果您不能说服默认的调度器执行所需的操作,则可以使用自定义调度器。
ehnmark 2011年

实际上,根据我发布的测试结果看来,我的担忧无效。
HuseyinUslu 2011年

0

我确实担心TPL对于受IO约束的案例会产生与顺序方法相似的结果。

我认为会的。瓶颈是什么?是解析还是下载?多线程处理对从Web下载没有太大帮助。

我将使用Task Parallel Library进行裁切,为下载的图像应用蒙版或特效,从播客中剪切一些样本等。它更具可伸缩性。

但这并不会加快数量级。花您的资源来实现一些功能,进行测试。

PS。“哇,我的函数在0.7秒而不是0.9秒内执行”;)


瓶颈在于下载内容。普通用户订阅了10多个提要,并且从我的测试中,至少需要15秒钟的时间-至少对于我的测试子集而言-用顺序方法下载并解析所有提要。我觉得有足够的改进空间。
HuseyinUslu 2011年

根据我发布的测试结果,似乎确实有足够的改进空间。
HuseyinUslu 2011年

0

如果您将对URL的调用并行化,那么即使只有一个核心,我认为它也会改善您的应用程序。看一下这段代码:

var client = new HttpClient();
var urls = new[]{"a", "url", "to", "find"};

// due to the EAP pattern, this will run in parallel.
var tasks = urls.Select(c=> client.GetAsync(c));

var result = Tasks.WhenAll(task).ContinueWith(a=> AnalyzeThisWords(a.Result));
result.Wait(); // don't know if this is needed or it's correct to call wait

在这种情况下,多线程与异步之间的区别在于回调/完成的方式。

使用EAP时,任务数与线程数无关。

当您依赖GetAsync任务时,http客户端将使用网络流(套接字,tcp客户端或任何其他客户端),并在BeginRead / EndRead完成时发信号通知它引发事件。因此,此刻不涉及线程。

调用完成后,可能会创建一个新线程,但是要由TaskScheduler(在调用GetAsync / ContinueWith调用中使用)创建新线程,使用现有线程或内联任务以使用调用线程。

如果AnalyzeThisWords阻塞太多时间,那么您将开始遇到瓶颈,因为ContinueWith上的“回调”是由线程池工作程序完成的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.