8

我有一个应用程序,它使用多线程同时执行 30 个独立任务,每个任务通过 http 检索数据,执行计算并将结果返回给 ui 线程。

我可以使用 TPL 执行相同的任务吗?

TPL 是创建 30 个新线程并将它们分布在所有可用内核上,还是只是将任务拆分到可用内核上并每个内核使用一个线程?

在这种情况下,使用 TPL 而不是多线程会提高性能吗?

4

4 回答 4

10

作为一般规则,没有什么可以阻止 TPL 使用比内核更多(或更少)的线程。

为了在某​​种程度上使用 TPL 来控制这种情况,我的第一种方法是:确保线程池最大线程设置至少为 30,然后以最大并发级别30 并行化任务。在任务中,您可以在执行之前使用信号量启动 CPU 密集型计算以将并发性限制为内核数。如果您不在 IIS 或 SQL 服务器下运行,您可以并且可能希望将线程池线程的最小/最大数量设置为 30,以防止线程池启发式算法过多地使用线程数。(当然,前提是 TPL 和线程池在此期间在您的应用程序中不用于其他目的。)

最佳线程数取决于具体情况。考虑例如您的场景:您的任务在检索数据时不受 CPU 限制 - 它们受网络限制。开始任务时,明智的做法是增加并行度,以便同时进行下载。但是,您的计算可能受 CPU 限制。在这种情况下,减少线程数以使每个内核只运行一个线程可能会产生更好的性能。

TPL 现在基于新的CLR 线程池
线程池使用启发式方法来决定线程数。
有一个关于新线程池的Channel9 视频有一些见解。
旧线程池的启发式和一些关于新线程池的信息可以在这里找到(最后一段“未来会怎样?”)

算法和数字在 CLR 的不同版本中会发生变化。
未来也可能如此。

关于并发级别的帖子有很多,我遇到的一个是here

于 2010-03-26T08:38:12.613 回答
9

我相信 TPL 通常每个核心使用一个线程,除非您明确告诉它使用更多。它可能会检测到什么时候这还不够——例如,在您的情况下,您的任务将花费大部分时间等待数据。

有什么理由不能使用异步 Web 抓取?我怀疑这里不需要每个任务都有一个线程,甚至每个核心都不需要一个线程。TPL 使异步编程的各个方面变得更容易,例如延续。

就效率而言,您的应用程序实际上是否受 CPU 限制?听起来您需要在网络端获得最大适当级别的并行性 - 这是需要集中精力的一点,除非计算真的很重量级。


更新 - 不是来自原作者

上面的答案一如既往地好,但可能会产生误导,因为它在 .NET 4.0 CLR 中没有一些重要的变化。

正如 Andras 所说,当前的 TPL 实现使用线程池,因此将根据需要使用尽可能多的线程(内核数量现在无关紧要):

任务并行库 (TPL) 是一组新类,专门设计用于更轻松、更高效地在现代硬件上执行非常细粒度的并行工作负载。TPL 作为 CTP 已经单独提供了一段时间,并且包含在 Visual Studio 2010 CTP 中,但在这些版本中,它基于自己的专用工作调度程序构建的。对于 CLR 4.0 的 Beta 1, TPL 的默认调度程序将是 CLR 线程池,它允许 TPL 风格的工作负载与现有的、基于 QUWI 的代码“玩得很好”,并允许我们在线程池——特别是线程注入算法,我们将在以后的文章中讨论。

从:

http://blogs.msdn.com/b/ericeil/archive/2009/04/23/clr-4-0-threadpool-improvements-part-1.aspx

于 2010-03-26T08:21:03.447 回答
2

我有一个应用程序,它使用多线程同时执行 30 个独立任务,每个任务通过 http 检索数据,执行计算并将结果返回给 ui 线程。

那是一个 IO-bound 并发程序。

我可以使用 TPL 执行相同的任务吗?

你可以,但是 TPL 是为 CPU 绑定的并行程序设计的,所以你会滥用它。

TPL 是创建 30 个新线程并将它们分布在所有可用内核上,还是只是将任务拆分到可用内核上并每个内核使用一个线程?

两者都不。TPL 本质上使用每核无等待工作窃取任务队列来动态负载平衡 CPU 密集型计算,因为它们运行。

在这种情况下,使用 TPL 而不是多线程会提高性能吗?

您将节省 30 个线程创建和不必要的线程产生的额外争用。

您的问题的正确解决方案是编写一个不阻塞线程的异步程序。这是通过将下载完成后的剩余计算表示为下载完成时使用数据调用的延续来完成的。

Microsoft 的新 F# 编程语言包括专门设计用于简化此操作的功能。例如,在 F# 中只需 5 行代码即可解决您的问题:

let fetchCalcAndPost uris calc post =
  for uri in uris do
    async { use client = new System.Net.WebClient()
            let! data = client.AsyncDownloadString uri
            do calc data |> post }
    |> Async.Start

该解决方案从不阻塞任何线程,因此它是完全并发的。

于 2010-05-16T16:07:26.710 回答
0

你会产生 30 个线程吗?你使用线程池吗?我相信你的 tpl 会更加优化。产生线程是一项相当昂贵的操作。我同意 Jon 的观点,即 tpl 通常每个核心使用一个线程。顺便说一句,我们在这里谈论的是什么 .NET 版本

于 2010-03-26T08:27:25.193 回答