【问题标题】:Waiting Threads Resource Consumption等待线程资源消耗
【发布时间】:2016-07-07 00:00:45
【问题描述】:

我的问题:

当线程处于TIMED_WAIT 状态(非睡眠)>99.9% 的时间时,JVM 中的大量线程是否会消耗大量资源(内存、CPU)?当线程在等待时,如果需要的话,维护它们需要多少 CPU 开销?

答案是否也适用于非 JVM 相关环境(如 linux 内核)?

上下文:

我的程序收到大量占用空间的包。它在不同的包中存储相似属性的计数。在收到包裹后的给定时间段(可能是几小时或几天)后,该特定包裹将过期,并且该包裹贡献的任何计数都应减少。

目前,我通过将所有包存储在内存或磁盘中来实现这些功能。每 5 分钟,我从存储中删除过期的包,并扫描剩余的包以计算属性。这种方法会占用大量内存,并且时间复杂度很差(O(n) 表示时间和内存,其中 n 是未过期包的数量)。这使得程序的可扩展性很差。

解决此问题的另一种方法是在每次收到包时增加属性计数,并启动一个Timer() 线程,在包过期后减少属性计数。这消除了存储所有大包的需要并将时间复杂度降低到O(1)。但是,这会产生另一个问题,因为我的程序将开始拥有O(n) 线程数,这可能会降低性能。由于大多数线程在其生命周期的绝大部分时间里都处于TIMED_WAIT 状态(Java 的Timer() 调用Object.wait(long) 方法),它是否仍然会对CPU 产生很大的影响?

【问题讨论】:

  • 您认为有多少线程可能正在等待?几百个左右可能不会对内核进行检查线程以及何时需要安排它们的负担,但是如果您要排队 500+,您可能需要重新考虑您的方法..
  • 我可能会有几百个。您能否解释一下为什么内核必须不断检查TIMED_WAIT 上的线程?我试图查找有关内核如何专门执行此操作的信息,但找不到任何所需的信息。
  • ScheduledExecutor / 一个优先级队列,按单线程过期时间戳。

标签: java multithreading performance timer


【解决方案1】:

首先,一个 Java(或 .NET)线程!= 一个内核/操作系统线程。

Java Thread 是一个高级包装器,它抽象了系统线程的一些功能;这些类型的线程也称为托管线程。在内核级别,线程只有两种状态,运行和未运行。内核会跟踪一些管理信息(堆栈、指令指针、线程 ID 等),但在内核级别没有线程处于 TIMED_WAITING 状态(.NET 相当于WaitSleepJoin 状态)。那些“状态”只存在于那些类型的上下文中(部分原因是 C++ std::thread 没有 state 成员)。

话虽如此,当一个托管线程被阻塞时,它会以几种方式完成(取决于它被请求在托管级别被阻塞的方式);我在 OpenJDK 中看到的线程代码实现利用信号量来处理托管等待(这是我在其他具有“托管”线程类的 C++ 框架以及 .NET Core 中看到的)库),并将互斥锁用于其他类型的等待/锁定。

由于大多数实现将使用某种锁定机制(如信号量或互斥锁),内核通常会做同样的事情(至少在您的问题涉及的地方);也就是说,内核将从“运行”队列中取出线程并将其放入“等待”队列(context switch)。进入线程调度,特别是内核如何处理线程的执行超出了本问答的范围,特别是因为你的问题是关于 Java 和 Java 可以在相当多不同类型的操作系统上运行(每个都处理线程完全不同)。

更直接地回答您的问题:

当线程处于 TIMED_WAIT 状态(非睡眠)>99.9% 的时间时,JVM 中的大量线程是否会消耗大量资源(内存、CPU)?

对此,有两点需要注意:创建的线程会消耗 JVM 的内存(堆栈、ID、垃圾收集器等),而内核会消耗内核内存来管理内核级别的线程。除非您特别说明,否则消耗的内存不会改变。所以如果线程在休眠或者运行,内存是一样的。

CPU 会根据线程活动和请求的线程数量而变化(请记住,线程也消耗内核资源,因此必须在内核级别进行管理,因此必须处理的线程越多,必须消耗更多的内核时间来管理它们)。

请记住,调度和运行线程的内核时间非常少(这是设计要点的一部分),但如果您计划运行 lot,这仍然是需要考虑的事情线程;此外,如果您知道您的应用程序将在只有几个内核的 CPU(或集群)上运行,那么您可用的内核越少,内核切换上下文的次数就越多,通常会增加额外的时间。

当线程在等待时,如果需要的话,维护它们需要多少 CPU 开销?

没有。见上文,但用于管理线程的 CPU 开销不会根据线程上下文而改变。额外的 CPU 可能会用于上下文切换,并且在活动时线程本身肯定会使用额外的 CPU,但是 CPU 没有额外的“成本”来维护等待线程与正在运行的线程.

答案是否也适用于非 JVM 相关环境(如 linux 内核)?

是和不是。如前所述,托管上下文通常适用于大多数此类环境(例如 Java、.NET、PHP、Lua 等),但这些上下文可能会有所不同,并且线程习语和一般功能取决于所使用的内核。因此,虽然一个特定的内核可能能够为每个进程处理 1000 多个线程,但有些内核可能有硬限制,而另一些内核可能存在其他问题,即每个进程的线程数较高;您必须参考操作系统/CPU 规范,看看您可能有什么样的限制。

由于大多数线程在其生命周期的绝大部分时间里都处于 TIMED_WAIT 状态(Java 的 Timer() 调用 Object.wait(long) 方法),它是否仍然会对 CPU 产生很大的影响?

不是(部分线程被阻塞),但需要考虑:如果(极端情况)所有(或>50%)线程需要同时运行怎么办?如果你只有几个线程管理你的包,那可能不是问题,但假设你有 500+; 250 个线程同时被唤醒会导致大量 CPU 争用。

由于您尚未发布任何代码,因此很难对您的方案提出具体建议,但人们倾向于将属性结构存储为类并将该类保存在可以引用的列表或哈希映射中在Timer(或单独的线程)中查看当前时间是否与包的过期时间匹配,然后“过期”代码将运行。这将线程数减少到 1,将访问时间减少到 O(1);但同样,如果没有代码,该建议可能不适用于您的场景。

希望对您有所帮助。

【讨论】:

  • 很好的解释。
  • 感谢您的解释。正如您所说,内核中运行和可运行线程没有消耗差异。但是如果“无线程”和可运行线程怎么办。我的意思是,对于许多 RPC 框架,我们必须指定一个线程池,并且我们总是想处理峰值请求,所以我们指定了一个较大的线程池 min-size 值,这会创建大量处于 TIME_WAITING 状态的线程,这些线程是否会导致显着更差的 CPU 消耗?
  • @Jaskey 在线程池情况下,您将拥有额外的 CPU 和内存供池使用,以跟踪线程并在将动作队列传递给正在运行的线程时处理动作队列。并且对于创建/销毁的任何其他线程都会有额外的 CPU 和内存,但是对于处于TIME_WAITING 状态的各个线程本身不会比处于相同状态的任何其他线程更影响 CPU。简而言之,池的底层线程就像任何其他线程一样,但线程池本身会消耗额外的资源。
  • @txtechhelp 感谢您的回复,但我仍然无法得到答案。例如,在处理 200 个请求后,我为应用程序 A 创建了一个包含 200 个线程(最小大小=200)的线程池。之后,200 个线程处于 TIME_WAITING 中。与另一个应用程序 B 在 TIME_WAITING 中只有一个线程相比,应用程序 A 的 CPU 消耗是否比 B 多?
  • @Jaskey 简而言之,没有。如果,且仅当,A 和 B 都处于 TIMED_WAITING 状态时,应用 A 不会使用比应用 B 更多的 CPU。不过应该注意的是,即使像 AMD Ryzen 这样具有 128 个内核的设备,如果应用程序 A 在线程池中有 200 个线程,您还有其他问题需要注意,比如绝对需要 200 个线程池线程,但这是一个不同的问答。
猜你喜欢
  • 1970-01-01
  • 2021-03-16
  • 2020-09-18
  • 1970-01-01
  • 2012-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-30
相关资源
最近更新 更多