等待线程的资源消耗

20

我的问题:

当JVM中的线程在TIMED_WAIT状态(非休眠)占用99.9%以上的时间时,大量的线程会消耗很多资源(内存、CPU),在等待状态下,如果需要维护它们,那么它们会产生多少CPU开销(如果有的话)?答案是否也适用于非JVM相关环境(如Linux内核)?

背景:

我的程序接收大量占用空间的包。它会存储不同包中相似属性的计数。在接收到包一定时间后(可能是几个小时或几天),特定的包将过期,对应的任何计数都应该被减少。

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

解决此问题的另一种替代方法是,每次一个包到来时都增加属性计数,并启动一个Timer()线程,在包过期后减少属性计数。这样可以消除存储所有笨重的包的需求,并将时间复杂度降低到O(1)。然而,这会创建大量的O(n)线程,可能会影响性能。由于大多数线程在它们的生命周期中都处于TIMED_WAIT状态(Java的Timer()调用Object.wait(long)方法),那么CPU是否仍然会受到非常大的影响?


你认为可能会有多少个线程在等待?大约几百个可能不会对内核检查线程及其需要被调度的负担太大,但如果你排队超过500个,你可能需要重新考虑你的方法.. - txtechhelp
我可能会有超过数百个。您能否解释一下为什么内核必须不断检查“TIMED_WAIT”线程?我试图找到内核如何特别执行此操作的信息,但找不到任何理想的信息。 - SegFault
1
ScheduledExecutor / 一个按过期时间戳优先级排序的带有单个线程的优先队列。 - zapl
1个回答

36

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

Java Thread是一个高级别的封装,抽象出了一些系统线程的功能;这些线程也被称为托管线程。在内核层面上,线程只有两个状态:运行和未运行。内核会跟踪一些管理信息(堆栈、指令指针、线程ID等),但是在内核层面上并不存在处于TIMED_WAITING状态的线程(对应于.NET中的WaitSleepJoin状态)。这些“状态”只存在于这些上下文中(这也是C++std::thread没有state成员的原因之一)。

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

由于大多数实现将使用某种锁定机制(如信号量或互斥锁),内核通常会执行同样的操作(至少关于您的问题而言)。也就是说,内核将从“运行”队列中取出线程并将其放入“等待”队列(进行上下文切换)。深入研究线程调度以及内核如何处理线程的执行超出了这个问答的范畴,特别是因为您的问题涉及Java,而Java可以在许多不同类型的操作系统上运行(每个操作系统都会完全不同地处理线程)。

更直接地回答您的问题:

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

对此,有几个要注意的事项:创建的线程会消耗JVM的内存(堆栈、ID、垃圾回收器等),而内核会消耗内核内存来管理线程。除非您明确说明,否则消耗的内存不会改变。因此,如果线程正在休眠或运行,内存是相同的。

CPU的使用情况将根据线程活动和请求的线程数而发生变化(请记住,一个线程还会消耗内核资源,因此必须在内核级别进行管理,这意味着需要处理的线程越多,就必须消耗更多的内核时间来管理它们)。

请记住,内核调度和运行线程所需的时间非常微小(这是设计的一部分),但如果您打算运行大量线程,则仍需考虑这一点。此外,如果您知道应用程序将在只有少数核心的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);但是,没有代码,这样的建议可能无法在您的情况下起作用。

希望这有所帮助。

很好的解释。 - hakunami
@Jaskey 在线程池的情况下,需要额外的CPU和内存来跟踪线程并处理动作队列以将其传递给正在运行的线程。同时,为了创建/销毁任何附加线程,还需要额外的CPU和内存,但是对于处于“TIME_WAITING”状态的单个线程本身而言,并不比同一状态下的其他线程消耗更多的CPU资源。简而言之,线程池的基础线程就像任何其他线程一样运行,但线程池本身将消耗额外的资源。 - txtechhelp
@txtechhelp 谢谢您的回复,但我仍然无法得到答案。例如,我为应用程序A创建了一个线程池,其中包含200个线程(最小大小=200)。在它处理完200个请求后,有200个线程处于TIME_WAITING状态。与另一个只有一个线程处于TIME_WAITING状态的应用程序B相比,应用程序A的CPU消耗是否更高? - JaskeyLam
@txtechhelp 所以简而言之,更多处于TIME_WAITING状态的线程不会消耗更多的CPU,但更多的线程确实会消耗更多的其他资源,比如内存,对吗? - JaskeyLam
@Jaskey 没错!简而言之,你已经有了想法。更深入的解释涉及内核设计,并且需要更多的内容,无法在此论坛上进行。非常幸运的是,有大量免费的资源可以帮助整合内核设计内存规格CPU 架构等等。 - txtechhelp
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接