13

我正在编写一个必须发出数千个网络请求的实用程序。每个请求只收到一个小数据包作为响应(类似于 ping),但可能需要几秒钟才能完成。处理每个响应在一行(简单)代码中完成。

这样做的最终结果是计算机不受 IO 限制、文件系统限制或 CPU 限制,它仅受响应延迟的限制。

这类似于,但一样有一种方法可以确定理想的线程数吗?Java 确定最佳线程数的最佳方法 [重复] ...主要区别在于我只受延迟的约束。

我正在使用一个ExecutorService对象来运行线程并使用一个Queue<Future<Integer>>来跟踪需要检索结果的线程:

ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
Queue<Future<Integer>> futures = new LinkedList<Future<Integer>>();

for (int quad3 = 0 ; quad3 < 256 ; ++quad3) {
    for (int quad4 = 0 ; quad4 < 256 ; ++quad4) {
        byte[] quads = { quad1, quad2, (byte)quad3, (byte)quad4 };
        futures.add(executorService.submit(new RetrieverCallable(quads)));
    }
}

...然后我将队列中的所有元素出列并将结果放入所需的数据结构中:

int[] result = int[65536]
while(!futures.isEmpty()) {
    try {
        results[i] = futures.remove().get();
    } catch (Exception e) {
        addresses[i] = -1;
    }
}

我的第一个问题是:这是跟踪所有线程的合理方法吗?如果线程 X 需要一段时间才能完成,那么许多其他线程可能会在 X 之前完成。线程池是否会在等待打开的插槽时耗尽自身,或者ExecutorService对象是否会以这样的方式管理池,将已完成但尚未处理的线程移出可用插槽,以便其他线程开始?

我的第二个问题是我可以使用哪些准则来找到进行这些调用的最佳线程数?我什至不知道这里的数量级指导。我知道它在 256 个线程上工作得很好,但似乎在 1024 个线程上花费的总时间大致相同。CPU 利用率徘徊在 5% 左右,因此这似乎不是问题。有了这么多线程,我应该查看哪些指标来比较不同的数字?显然,处理批处理的总时间,每个线程的平均时间......还有什么?内存是这里的问题吗?

4

7 回答 7

8

它会让你震惊,但你不需要任何线程用于 I/O(从数量上讲,这意味着 0 个线程)。很高兴您研究了多线程不会增加您的网络带宽。现在,是时候知道线程进行计算了。他们没有进行(高延迟)通信。通信由网络适配器执行,这是另一个进程,与 CPU 真正并行运行。分配一个线程(看看这位声称您需要 1 个线程的先生列出了哪些分配的资源只是为了休眠直到网络适配器完成其工作是愚蠢的。您不需要 I/O 线程 = 您需要 0 个线程。

分配用于计算的线程以与 I/O 请求并行进行是有意义的。线程的数量将取决于计算与通信的比率,并受 CPU 中的核心数量的限制

对不起,我不得不说,尽管你已经暗示了阻塞 I/O 的承诺,但是很多人不理解这个基本的东西。接受建议,使用异步 I/O,您会发现问题不存在。

于 2013-10-24T12:04:40.880 回答
7

正如您提到的一个链接答案中提到的那样,Brian Goetz在他的文章中很好地介绍了这一点。

他似乎暗示在您的情况下,建议您在提交线程数之前收集指标。

调整池大小

调整线程池的大小主要是为了避免两个错误:线程太少或线程太多。...

线程池的最佳大小取决于可用处理器的数量和工作队列中任务的性质。...

对于可能等待 I/O 完成的任务(例如,从套接字读取 HTTP 请求的任务),您需要将池大小增加到超过可用处理器的数量,因为并非所有线程都能正常工作每时每刻。使用 profiling,您可以估计典型请求的等待时间 (WT) 与服务时间 (ST) 的比率。如果我们将此比率称为 WT/ST,对于 N 处理器系统,您将需要大约 N*(1+WT/ST) 线程以保持处理器充分利用。

我的重点。

于 2013-10-24T10:18:15.783 回答
3

您是否考虑过使用Actors

最佳实践。

  • 演员应该像好同事一样:高效地完成工作,不要不必要地打扰其他人,避免占用资源。转换为编程,这意味着以事件驱动的方式处理事件并生成响应(或更多请求)。Actor 不应阻塞(即在占用线程时被动等待)某个外部实体(可能是锁、网络套接字等),除非它是不可避免的;在后一种情况下,请参见下文。

抱歉,我不能详细说明,因为没有太多用过这个。

更新

回答Akka 的良好用例可能会有所帮助。
Scala:为什么 Actor 是轻量级的?

于 2013-10-24T10:12:38.743 回答
2

可以肯定的是,在所描述的情况下,最佳线程数是 1。事实上,对于“我应该使用多少线程”形式的任何问题,这通常是令人惊讶的答案?

每个额外的线程都会在堆栈(和相关的 GC 根)、上下文切换和锁定方面增加额外的开销。这可能是可衡量的,也可能是不可衡量的:在所有目标环境中有意义地衡量它的努力是不平凡的。作为回报,几乎没有提供任何好处的空间,因为处理既不受 CPU 限制,也不受 io 限制。

因此,即使只是为了降低风险,越少越好。而且你不能少于1。

于 2013-10-24T10:37:43.090 回答
1

在我们的高性能系统中,我们使用@Andrey Chaschev 描述的参与者模型。

没有。Actor 模型中的最佳线程数因您的 CPU 结构以及每个盒子运行多少进程 (JVM) 而异。我们的发现是

  1. 如果您只有 1 个进程,请使用总 CPU 核心数 - 2。
  2. 如果您有多个进程,请检查您的 CPU 结构。我们发现没有它很好。线程数 = 没有。单个 CPU 中的内核数量 - 例如,如果您有一个 4 CPU 服务器,每个服务器有 4 个内核,那么每个 JVM 使用 4 个线程可以为您提供最佳性能。之后,始终为您的操作系统保留至少 1 个核心。
于 2013-10-24T11:05:02.470 回答
1

我假设所需的优化是处理所有请求的时间。您说请求的数量是“数千”。显然,最快的方法是一次发出所有请求,但这可能会溢出网络层。您应该确定网络层可以同时承受多少个连接,并将这个数字作为您的程序的参数。

然后,为每个请求花费一个线程需要大量内存。您可以使用非阻塞套接字来避免这种情况。在 Java 中,有 2 个选项:带有选择器的 NIO1 和带有异步通道的 NIO2。NIO1 比较复杂,最好找一个现成的库,复用。NIO2 很简单,但仅从 JDK1.7 开始可用。

处理响应应该在线程池上完成。我不认为线程池中的线程数会极大地影响您的整体性能。只需将线程池大小从 1 调整到可用处理器的数量。

于 2013-10-24T11:21:29.320 回答
0

部分答案,但我希望它有所帮助。是的,内存可能是个问题:Java 默认保留 1 MB 的线程堆栈(至少在 Linux amd64 上)。所以在你的盒子里有几 GB 的 RAM,这会将你的线程数限制在几千个。

您可以使用类似的标志来调整-XX:ThreadStackSize=64它。这将为您提供 64 kB,这在大多数情况下已经足够了。

您还可以完全摆脱线程并使用 epoll来响应传入的响应。这更具可扩展性,但我没有在 Java 中执行此操作的实际经验。

于 2013-10-24T09:46:58.453 回答