【问题标题】:Design considerations for an adaptive thread pool in JavaJava 中自适应线程池的设计注意事项
【发布时间】:2010-11-12 03:09:16
【问题描述】:

我想用 Java 实现一个线程池,它可以根据提交给它的任务的计算和 I/O 行为动态调整自身大小。

实际上,我想实现与 C# 4.0 中的 new Thread Pool implementation 相同的行为

是否已经有实现,或者我可以通过使用大多数现有的并发实用程序(例如 CachedThreadPool)来实现此行为?

C# 版本进行自我检测以实现最佳利用率。 Java 中提供了哪些自我检测,目前对性能有何影响?

采用协作方法是否可行,其中任务表明其意图(例如进入 I/O 密集型操作,进入 CPU 密集型操作阶段)?

欢迎提出任何建议。

编辑基于 cmets:

目标场景可能是:

  • 本地文件抓取与处理
  • 网页抓取
  • 多网络服务访问和聚合

CachedThreadPool 的问题是它会在所有现有线程都被阻塞时启动新线程——你需要在它上面设置明确的界限,但仅此而已。

例如,我有 100 个 Web 服务要连续访问。如果我创建一个 100 CTP,它将启动 100 个线程来执行操作,并且大量的多个 I/O 请求和数据传输肯定会互相绊倒。对于静态测试用例,我可以进行试验并找出最佳池大小,但我希望它能够自适应地确定并以某种方式应用。

【问题讨论】:

  • 我不确定您打算从缩小的线程池中获得什么好处。一个线程什么都不做的成本几乎为零(最大的影响可能来自线程本地缓存)。
  • 与 CachedThreadPool 的线程超时选项提供的好处相同。
  • 你的意思是像缓存线程池一样吗?默认空闲超时为一分钟,但您可以更改。
  • 也许您可以解释您正在寻找的行为?我点击了链接,它只是说它有多酷,但没有说它是什么。
  • 不知道为什么要检测线程,也许它在 Java 中与 C# 的含义不同...

标签: c# java concurrency threadpool


【解决方案1】:

我认为您应该以特定于平台的方式监控 CPU 利用率。找出您有多少 CPU/内核,并监控负载。当您发现负载较低,而您还有更多工作时,请创建新线程 - 但不要超过 num-cpus 的 x 倍(例如 x=2)。

如果你真的想考虑IO线程,试着找出每个线程在你的池耗尽时处于什么状态,并从总数中减去所有等待的线程。不过,风险之一是您承认过多的任务会耗尽记忆。

【讨论】:

  • 我想这是我的问题之一,如何检测我的每个线程(以独立于平台的方式)的繁忙和阻塞率。
  • 我并不是要建议你检测线程。相反,使用 OS API 从线程外部找出线程是否正在等待。
  • 我认为这叫做仪器。
  • 不不不。插装是当您将代码放入线程本身时,即在线程中运行的代码中放入更多代码。我会避免这样做,并使用审讯而不是仪器。
  • 我建议你不要使用任何标准API,也不要任何注入。在 Linux 上,读取 /proc//task//stat 以了解线程是否正在运行。
【解决方案2】:

给出的例子是

Result[] a = new Result[N];
for(int i=0;i<N;i++) {
    a[i] = compute(i);
}

在 Java 中,将其并行化到每个空闲内核并动态分配工作负载的方式,因此一个任务是否比另一个任务花费更长的时间并不重要。

// defined earlier
int procs = Runtime.getRuntime().availableProcessors();
ExecutorService service = Executors.newFixedThreadPool(proc);

// main loop.
Future<Result>[] f = new Future<Result>[N];
for(int i = 0; i < N; i++) {
    final int i2 = i;
    a[i] = service.submit(new Callable<Result>() {
        public Result call() {
            return compute(i2);
        }
    }
}
Result[] a = new Result[N];
for(int i = 0; i < N; i++) 
    a[i] = f[i].get();

这在过去 5 年中没有太大变化,因此它不像首次推出时那么酷。 Java 真正缺乏的是闭包。如果这确实是个问题,您可以使用 Groovy。

附加:如果您关心性能,而不是作为示例,您可以并行计算斐波那契,因为它是一个很好的函数示例,如果您单线程计算它会更快。

一个区别是每个线程池只有一个队列,所以不需要偷工作。这可能意味着每个任务的开销更大。但是,只要您的任务通常需要超过 10 微秒,就没有关系。

【讨论】:

  • 其实你的回答和我的问题无关。我知道如何使用固定池和缓存池来做到这一点,但我想要介于两者之间的东西 - 将 CPU 利用率、I/O 阻塞和 I/O 传输属性考虑在内并“安排”事情以实现最大 CPU 和最大 I例如,通过适当的交错来利用 /O。
  • 您可以通过覆盖 beforeTask 和 afterTask 或使用另一个线程来监控队列长度和池中的线程数来做到这一点。然而,在尝试了这些方法之后,我只是建议你增加可用线程的数量,并让操作系统调度程序找出分配任务的最佳方式(因为它知道其他应用程序、cpu 使用情况、io 使用情况等)
  • 总之,您所建议的是操作系统计划的设计目的,并且它在 Windows、Linux 和 solaris 上运行良好。如果您不信任操作系统调度程序,您只会尝试自己编写。所以我的回答就是你问题的答案。即不用担心,操作系统可以工作!
  • 我同意,调度程序很好,尤其是 W7 的,但我不知道 OS 调度程序的目标函数是否与我的组合目标函数相当:不要通过大量同时传输来压倒 I/O并且不要用持续的唤醒/阻塞来压倒线程本身,并且不要用比核心更多的线程来减慢计算密集型部分。我想我必须尝试一下我目前的一些想法,我们会看到的。我想我也会很快提供赏金。
  • 我可以建议的是 CPU 密集型任务应该具有与内核相同数量的线程(见上文),磁盘 IO 密集型任务应该具有与驱动器相同数量的线程(通常为 1) ,对于网络 IO,您应该允许连接,直到响应时间太慢(这是主观的)
【解决方案3】:

考虑创建一个关键是瓶颈资源的 Map。

每个提交到池中的线程都会提交一个资源,这是它的瓶颈,即“CPU”、“网络”、“C:\”等。

您可以从每个资源只允许一个线程开始,然后慢慢增加,直到工作完成率停止增加。 CPU 之类的东西可能会限制核心数量。

【讨论】:

  • 如果你要做这样的事情,请确保使用 Map 的同步实现。
【解决方案4】:

让我介绍一种替代方法。拥有单个线程池是一个很好的抽象,但它的性能不是很好,尤其是当作业非常受 IO 限制时 - 没有好的方法来调整它,很容易炸毁池大小以最大化 IO 吞吐量,但你会受苦由于线程切换过多等原因。

相反,我建议查看 Apache MINA 框架的架构以获得灵感。 (http://mina.apache.org/) 这是一个高性能的 Web 框架 - 他们将其描述为一个服务器框架,但我认为他们的架构也适用于逆向场景,例如蜘蛛和多服务器客户端。 (实际上,您甚至可以为您的项目开箱即用地使用它。)

他们对所有 IO 操作使用 Java NIO(非阻塞 I/O)库,并将工作划分为两个线程池:一组小而快的套接字线程,以及一组更大、更慢的业务逻辑线程。所以图层如下所示:

  • 在网络端,一大组 NIO 通道,每个通道都有一个消息缓冲区
  • 一个小的套接字线程池,它们通过通道列表循环。他们唯一的工作是检查套接字,并将任何数据移出消息缓冲区 - 如果消息完成,则将其关闭并转移到作业队列。这些家伙很快,因为他们只是推动位,并跳过任何在 IO 上阻塞的套接字。
  • 序列化所有消息的单个作业队列
  • 一个大型处理线程池,用于从队列中提取消息、解析它们并执行所需的任何处理。

这使得性能非常好 - IO 被分离到自己的层中,您可以调整套接字线程池以最大化 IO 吞吐量,并单独调整处理线程池以控制 CPU/资源利用率。

【讨论】:

  • 谢谢,我会尽快看看。
猜你喜欢
  • 2012-11-29
  • 1970-01-01
  • 1970-01-01
  • 2012-05-16
  • 1970-01-01
  • 1970-01-01
  • 2012-08-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多