【问题标题】:Should i use ThreadPools or Task Parallel Library for IO-bound operations我应该使用线程池还是任务并行库进行 IO 绑定操作
【发布时间】:2011-07-09 23:30:52
【问题描述】:

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

如果我使用顺序方法,考虑到大量资源,处理所有资源需要相当长的时间(因为网络问题和类似的东西);

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

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

ThreadPools 肯定会使用工作线程为我处理这项工作,我会得到我所期望的(在多核 CPU 环境中,也将使用其他内核)。

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

那么对于 IO-bound 操作(在我的例子中是从 web 读取资源),是最好使用 ThreadPools 并控制事物,还是更好地依赖 TPL? TPL 也可以用在 IO 密集型场景中吗?

更新:我主要担心的是 -- 在单核 CPU 环境中,TPL 会像顺序方法一样运行,还是仍会提供并发性?我已经在阅读Parallel Programming with Microsoft .NETbook,但找不到确切的答案。

注意:这是对我之前的问题 [Is it possible to use thread-concurrency and parallelism together?] 的重新措辞,措辞非常错误。

【问题讨论】:

    标签: c# multithreading task-parallel-library threadpool parallel-extensions


    【解决方案1】:

    所以我决定为此编写测试并在实际数据上进行查看。

    测试图例

    • Itr:迭代
    • Seq:顺序方法。
    • 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] -- 支持超线程 --

    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 具有优点,例如简单的异常处理、取消支持和轻松返回任务结果的能力。尽管并行扩展也是另一种可行的选择。

    自行运行测试

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

    更新:修复了源链接。

    【讨论】:

    • +1 使用科学方法解决您的问题。如果我没记错的话,上述所有技术都使用线程池,这解释了类似的结果。总的来说,我喜欢使用 Parallel Extensions,因为语法很简单而且我很懒。
    • 我已经进一步清理了源代码; github.com/raistlinthewiz/concurrency-tests
    • 感谢您完成所有测试工作。
    【解决方案2】:

    如果您想最大限度地提高 IO 绑定任务的吞吐量,您绝对必须将传统的异步处理模型 (APM) API 与基于 TPL 的工作结合起来。 APM API 是在异步 IO 回调挂起时解除阻塞 CPU 线程的唯一方法。 TPL 提供the TaskFactory::FromAsync helper method 来协助结合APM 和TPL 代码。

    查看 MSDN 上名为 TPL and Traditional .NET Asynchronous Programming 的 .NET SDK 部分,了解有关如何结合这两种编程模型以实现异步必杀技的更多信息。

    【讨论】:

      【解决方案3】:

      您是对的,当您创建自己的线程池时,TPL 确实删除了您拥有的一些控制权。但这仅在您不想深入挖掘时才正确。 TPL 确实允许您创建不属于 TPL 线程池的长时间运行的任务,并且可以很好地满足您的目的。免费阅读的已出版书籍Parallel Programming with Microsoft .NET 将使您更深入地了解如何使用 TPL。 您始终可以选择提供 Paralle.For,Tasks explicit 参数应该分配多少线程。除此之外,如果您想要完全控制,您可以用您自己的替换 TPL 调度程序。

      【讨论】:

        【解决方案4】:

        您可以将自己的 task scheduler 分配给 TPL 任务。不过默认的work stealing 非常聪明。

        【讨论】:

        • 我已经阅读了该资源,实际上我因此而担心;在 .NET Framework 4 中,默认任务调度程序与线程池紧密集成。如果使用默认任务调度程序,则执行并行任务的工作线程由 .NET ThreadPool 类管理。 通常,您计算机上的工作线程数至少与内核数一样多。 -- 如果我在单核环境中运行怎么办?我找不到任何澄清..
        • 所以你想强制最小并行度,例如在这种情况下,最后 X 度? TaskCreationOptions.LongRunning might 成为你想要的。如果您无法说服默认调度程序执行您想要的操作,自定义调度程序是另一种选择。
        • 实际上,根据我发布的测试结果,我的担忧是无效的。
        【解决方案5】:

        我确实担心 TPL 会为我的 IO 绑定案例产生与顺序方法相似的结果。

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

        我会使用任务并行库进行裁剪、为下载的图像应用蒙版或效果、从播客中剪切一些样本等。它更具可扩展性。

        但这不会是数量级的加速。花费你的资源来实现一些功能,测试。

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

        【讨论】:

        • 瓶颈是下载内容。一个普通用户订阅了 10 多个提要,并且从我的测试中,它需要 15 秒以上——至少对于我的测试子集来说——以顺序方法下载和解析所有这些提要。我觉得还有足够的改进空间。
        • 根据我发布的测试结果,似乎确实有足够的改进空间。
        【解决方案6】:

        如果您将调用并行化到 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 上的“回调”是由线程池工作程序完成的。

        【讨论】:

          猜你喜欢
          • 2015-09-14
          • 1970-01-01
          • 2020-09-15
          • 1970-01-01
          • 1970-01-01
          • 2016-06-11
          • 1970-01-01
          • 2014-03-28
          • 2012-06-24
          相关资源
          最近更新 更多