繁体   English   中英

基于可用的免费cpu的Java并发

[英]Java concurrency based on available FREE cpu

当且仅当有空闲cpu时,如何扩展以使用更多线程? 类似于ThreadPoolExecutor的东西,当cpu核心空闲时使用更多线程,如果没有则更少或仅使用一个线程。

使用案例

当前情况:我的Java服务器应用程序处理请求并提供结果。 有一个ThreadPoolExecutor按照以下原则为具有合理数量的最大线程的请求提供服务:cpu核心数=最大线程数。 执行的工作很重,并且有一些磁盘IO(DB)。 代码是线性的,单线程的。 单个请求需要50到500毫秒才能处理。 有时每分钟只有几个请求,有时同时有30个请求。 具有12个内核的现代服务器可以很好地处理负载。 吞吐量很好,延迟还可以。

期望的改进:当请求数量较少时,大多数情况下都是如此,许多cpu核心处于空闲状态。 在这种情况下,可以通过为单个请求多线程运行一些代码来改进延迟。 一些原型设计显示了改进,但是一旦我测试了更多的并发请求,服务器就会变成香蕉。 吞吐量下降,内存消耗过高。 30个同时请求共享队列10意味着10个最多可以运行而20个正在等待,并且10个中的每一个一次最多使用8个线程用于并行,对于具有12个核心的机器来说似乎太多了(其中6是虚拟的)。

在我看来,这似乎是一个常见的用例,但我无法通过搜索找到信息。

IDEAS

1)请求计数一个想法是计算当前处理的请求数。 如果为1或低则执行更多并行操作,如果为高则不执行任何操作并继续执行单线程,如前所述。 这听起来很简单。 缺点是:请求计数器重置不能包含错误,最后想一想。 它实际上并没有检查可用的cpu,也许另一个进程也使用cpu。 在我的情况下,机器专用于这个应用程序,但仍然。

2)实际的cpu查询我认为正确的方法是只询问cpu,然后再决定。 由于Java7有OperatingSystemMXBean.getSystemCpuLoad(),请参阅http://docs.oracle.com/javase/7/docs/jre/api/management/extension/com/sun/management/OperatingSystemMXBean.html#getSystemCpuLoad()但我可以找不到任何提到getSystemCpuLoad和ThreadPoolExecutor的网页,或类似的关键字组合,这告诉我这不是一条好路。 JavaDoc说“返回”整个系统最近的cpu使用情况“,我想知道”最近的cpu使用情况“是什么意思,最近的情况以及调用的成本。

UPDATE

我暂时搁置了这个问题,看看是否会有更多的输入。 不。 虽然我不喜欢技术问题的“无能为力”的答案,但我现在接受霍尔格的回答。 他有良好的声誉,良好的论据,其他人已经批准了他的答案。 我自己曾经尝试过2个想法。 我查询任务中的getSystemCpuLoad()来决定他们自己的ExecutorService有多大。 正如Holger所写,当存在SINGLE ExecutorService时,可以很好地管理资源。 但是一旦任务开始他们自己的任务,他们就不能 - 这对我来说没有用。

没有办法根据“免费CPU”进行限制,无论如何它都无法工作。 有关“免费CPU”的信息在您获得后立即过时。 假设您有12个并发运行的线程并同时检测到有一个空闲的CPU核心并决定安排子任务......

您可以做的是限制最大资源消耗,当使用具有所有任务的最大线程数的单个ExecutorService ,该消耗非常有效。

棘手的部分是任务对子任务的结果的依赖性,这些子任务在以后排队,并且由于工作线程的数量有限,可能仍然处于未决状态。

如果任务检测到其子任务仍处于挂起状态,则可以通过撤消并行执行来调整此值。 为此,请手动为子任务创建FutureTask ,并使用execute而不是submit安排它。 然后在正常情况下继续执行任务,并在顺序实现中执行子任务的位置检查是否可以从ThreadPoolExecutor remove FutureTask cancel不同,这只有在尚未启动的情况下才有效,因此它是一个没有空闲线程的指示器。 因此,如果remove返回true ,则可以就地执行子任务,让所有其他线程执行任务而不是子任务。 否则,您可以等待结果。

在这个地方值得注意的是,如果任务适应I / O操作(或者可能等待子任务),拥有比CPU核心更多的线程是可以的。 这里重要的是要有一个限制。

FutureTask<Integer> coWorker = new FutureTask<>(/* callable wrapping sub-task*/);
executor.execute(coWorker);

// proceed in the task’s sequence

if(executor.remove(coWorker)) coWorker.run();// do in-place if needed
subTaskResult=coWorker.get();

// proceed

听起来像Java 7中引入的ForkJoinPool正是您所需要的。 ForkJoinPool专门设计用于使所有CPU完全忙碌,这意味着存在与CPU一样多的线程,并且所有这些线程也在工作而不是阻塞(对于后者,请确保使用ManagedBlocker进行数据库查询)。

ForkJoinTask有一个方法getSurplusQueuedTaskCount ,JavaDoc getSurplusQueuedTaskCount说“这个值可能对于是否分叉其他任务的启发式决策很有用。” 因此可以更好地替代getSystemCpuLoad解决方案,以便对任务分解做出决策。 这允许您在系统负载较高时减少分解次数,从而减少任务分解开销的影响。

另请参阅我的答案以获得有关Fork / Join-pools原理的更深入的解释。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM