【问题标题】:executorService.invokeAll(...) takes longer time to finish even when timeout is set即使设置了超时,executorService.invokeAll(...) 也需要更长的时间才能完成
【发布时间】:2020-10-28 15:38:12
【问题描述】:

我遇到了一个不知道如何解决的问题。

我正在尝试并行化我们迄今为止按顺序完成的部分代码。为此,我将任务划分为几个较小的正交任务。

我已经创建了一个 executorService 并且正在运行:

executorService.invokeAll(callableList, timeBudget, TimeUnit.NANOSECONDS);

每个可调用对象中都有多个 IO 任务(例如访问数据库和外部服务),总时间预算为 200 毫秒 +-。使用invokeAll 的原因是因为我对所有请求都有一个总体时间预算。因此,我需要一种方法来通过单一预算限制所有期货。

为了测试我自己,我添加了不同的指标,这些指标可以报告给我们拥有的一些日志记录可视化工具。我注意到了:

  • 该部分代码的中位(和第 75 个百分位)延迟更快。
  • 第 95 个以上的百分位数实际上变得更糟了。

经过彻底调查(我对代码的不同部分进行了基准测试)后,我注意到invokeAll 第 99 个百分位的运行时间实际上是 500 毫秒,有时甚至更多。这件事真的搞砸了优化。关于可能导致这种情况的任何想法?还有其他建议吗? invokeAll 有替代品吗?

【问题讨论】:

  • 你的线程池有多大,有多少任务?在顺序模式下,每个任务需要多长时间?您是否添加了日志记录以确认您的 IO 操作正在阻塞线程,以便在这些外部调用中获得所需的并发性?你可能会饿死那些超过 95% 的线程。
  • 当您并行运行所有程序时,您可能还会遇到诸如耗尽的数据库连接池和 http 客户端池之类的限制。总体而言,您需要衡量并找到瓶颈。
  • @JoeW 我们可以将代码分为 3 个任务 - 数据库、请求和将所有线程合并为一个数据结构的代码。没有并行性的数据库任务大约需要 40 毫秒,而有并行性大约需要 20 毫秒。第三方请求耗时 60ms,并行耗时 45-50ms,合并任务几乎不需要。
  • @ewramner 的注释很好。另外,您可以发布执行程序服务的创建和配置吗?如果您使用较小的固定大小的线程池会发生什么?甚至可以使用 1 的线程池运行,以确保更新后的重构按预期运行,并且得到与历史顺序代码相同的结果,并且不会在某处挂起资源。
  • 您的任务可能已排队。您可以针对每个任务检测任务的创建时间、开始运行时间和结束时间。然后,您将获得开始减去创建的等待时间和结束减去创建的总时间。如果等待时间很长,则应归咎于固定线程池。编辑: System.currentTimeMillis() 应该足够好了。

标签: java concurrency parallel-processing executorservice


【解决方案1】:

虽然我不知道为什么 invokeAll 超时有时需要比给定预算更多的时间。我有一个问题的答案:如何在给定预算的情况下同时运行期货列表?

ListeningExecutorService executor = <init>;
List<ListenableFuture<G>> futures = new ArrayList<>();
for (T chunk : chunks) {
    futures.add(executorService.submit(() -> function(chunk, param1, param2, ...)));
}
Futures.allAsList(futures).get(budgetInNanos, TimeUnit.NANOSECONDS);

上面的代码使用了 Guava 库。

这种方法的问题是我没有得到每个未来的状态,因为如果时间到了,我会得到一个超时异常 - 但至少在预算方面,行为符合预期。

【讨论】:

    猜你喜欢
    • 2022-12-23
    • 2018-02-07
    • 2016-07-30
    • 2013-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多