我正在编写一个必须生成数千个网络请求的实用程序。每个请求只接收一个响应的小数据包(类似于ping),但可能需要几秒钟才能完成。处理每个响应在一个(简单)代码行中完成。
这样做的最终结果是计算机不受IO限制,受文件系统限制或受CPU限制,它只受响应延迟的约束。
这类似于,但不与There is a way to determine the ideal number of threads?和Java best way to determine the optimal number of threads [duplicate]相同......主要区别在于我只受延迟限制。
我使用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%左右,因此这似乎不是问题。有了这么大的线程,我应该考虑比较不同数字的所有指标是什么?显然处理批次的总时间,每个线程的平均时间......还有什么?记忆是个问题吗?
答案 0 :(得分:7)
它会震惊你,但是you do not need any threads for I/O(从数量上讲,这意味着0个线程)。你研究过多线程不会增加你的网络带宽是很好的。现在,是时候知道线程进行计算了。他们没有进行(高延迟)通信。通信由网络适配器执行,这是另一个进程,与CPU并行运行。 分配线程(请参阅which resources allocated are listed by this gentlemen who claims that you need 1 thread)只是为了睡眠直到网络适配器完成其工作才是愚蠢的。您不需要I / O线程=您需要0个线程。
分配用于计算的线程与I / O请求并行是有意义的。线程数量取决于computation-to-communication ratio和limited by the number of cores in your CPU。
对不起,我不得不说尽管你肯定暗示了阻止I / O的承诺,但很多人都不明白这个基本的东西。拿the advise, use asynchronous I/O,您会发现问题不存在。
答案 1 :(得分:5)
正如您所提及的其中一个链接答案中所述,Brian Goetz已在article中涵盖了这一点。
他似乎暗示在您的情况下,建议您在提交线程计数之前收集指标。
调整池大小
调整线程池的大小主要是避免两个错误:线程太少或线程太多。 ...
线程池的最佳大小取决于可用处理器的数量以及工作队列上任务的性质。 ...
对于可能等待I / O完成的任务 - 例如,从套接字读取HTTP请求的任务 - 您将希望将池大小增加到超过可用处理器的数量,因为并非所有线程将一直在工作。 使用性能分析,您可以估算典型请求的等待时间(WT)与服务时间(ST)的比率。如果我们将此比率称为WT / ST,对于N处理器系统,您将需要大约N *(1 + WT / ST)线程来保持处理器的充分利用。
我的重点。
答案 2 :(得分:3)
您是否考虑过使用Actors?
最佳做法。
- 演员应该像好伙伴一样:高效地完成工作 没有不必要地打扰其他人,避免占用 资源。转换为编程,这意味着处理事件和 以事件驱动的方式生成响应(或更多请求)。 参与者不应该阻塞(即占用线程时被动等待) 在一些外部实体上 - 可能是一个锁,一个网络套接字, 等 - 除非是不可避免的;在后一种情况下见下文。
抱歉,我无法详细说明,因为没有多少用过这个。
<强>更新强>
Good use case for Akka中的回答可能会有所帮助。
Scala: Why are Actors lightweight?
答案 3 :(得分:2)
在所描述的情况下,非常肯定,最佳线程数是1.实际上,这通常是对“我应该使用多少线程”这一形式的任何问题的答案?
每个附加线程在堆栈(和相关的GC根),上下文切换和锁定方面增加了额外的开销。这可能是可测量的,也可能是不可测量的:在所有目标环境中有意义地测量它的效果是非常重要的。作为回报,几乎没有提供任何好处的余地,因为处理既不是cpu也不是io-bound。
如果只是出于降低风险的原因,那么总是更好。你不能少于1。
答案 4 :(得分:1)
在我们的高性能系统中,我们使用@Andrey Chaschev所描述的演员模型。
没有。 actor模型中的最佳线程数与CPU结构和每个盒子运行的进程数(JVM)不同。我们的发现是
答案 5 :(得分:1)
我假设所需的优化是处理所有请求的时间。你说请求的数量是“数千”。显然,最快的方法是立即发出所有请求,但这可能会溢出网络层。您应该确定网络层可以承受多少个并发连接,并将此数字作为您的程序的参数。
然后,为每个请求花费一个线程需要大量内存。您可以使用非阻塞套接字来避免这种情况。在Java中,有两个选项:带选择器的NIO1和带异步通道的NIO2。 NIO1很复杂,所以最好找一个现成的库并重用它。 NIO2很简单,但仅在JDK1.7之后可用。
处理响应应该在线程池上完成。我不认为线程池中的线程数会大大影响您的整体性能。只需调整线程池大小从1到可用处理器的数量。
答案 6 :(得分:0)