4

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

实际上,我想实现与C# 4.0 中的新线程池实现相同的行为

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

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

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

欢迎任何建议。

编辑基于评论:

目标场景可能是:

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

CachedThreadPool 的问题在于,当所有现有线程都被阻塞时,它会启动新线程——您需要在其上设置显式边界,仅此而已。

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

4

4 回答 4

2

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

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

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

于 2009-07-27T20:32:11.187 回答
1

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

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

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

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

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

于 2009-07-30T16:51:43.433 回答
1

给出的例子是

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 微秒,就没有关系。

于 2009-07-19T19:28:20.580 回答
0

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

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

于 2009-07-19T17:48:25.477 回答