在我们日常编程的过程中,经常会遇到一个问题:线程的sleep时间似乎并不像我们想象的那样准确,比如我们让线程sleep 100ms,实际sleep的时间经常是大于这个时间的。那么,为什么线程的sleep时间不准确呢?让我们来一探究竟。
线程状态:生命周期的五个阶段
首先我们需要了解一下线程的生命周期,线程的生命周期可以分为5个状态:
- 新生状态:线程对象被创建出来,但还没有开始运行。想象一下,这就像你买了一台新电脑,但还没有开机启动一样。
- 就绪状态:线程已经准备好运行,只是在等待系统分配CPU资源。这就像你已经打开了电脑,程序加载完毕,只等你开始操作一样。
- 运行状态:线程正在执行自己的代码,就像你正在使用电脑进行各种操作一样。
- 阻塞状态:线程暂时停止执行,等待某些条件满足后再继续运行。这就像你正在玩电脑游戏,突然有人打电话来,你需要暂停游戏去接电话一样。
- 死亡状态:线程执行完毕或被强制终止,就像你关闭电脑一样。
Sleep时间不准确的原因
休眠并非即刻醒来
当我们调用线程的sleep方法时,线程会进入阻塞状态,等待指定的时间后再恢复到就绪状态。然而,这个"恢复到就绪状态"并不意味着线程立即就会开始运行,它还需要等待系统分配CPU资源。因此,实际的sleep时间可能会超出我们设定的时间,这就是线程sleep时间不准确的主要原因。
上下文切换的额外消耗
此外,还有一些其他因素也会影响线程sleep的准确性,例如上下文切换的开销。每次线程切换,都需要保存当前线程的状态(例如寄存器的值、执行的地址空间等)并加载新线程的状态,这个过程是需要时间的。同时,CPU缓存中的数据也可能因为线程切换而失效,从而导致CPU需要从内存中重新读取数据,这也会增加额外的时间开销。
线程的调度:谁先运行
上面我们提到线程就绪后等待系统分配CPU资源是sleep时间超出的主要原因。那么,系统是如何决定哪个线程应该获得CPU资源的呢?这就涉及到了线程调度的问题。
线程调度是操作系统中的一项重要功能,其目标是使系统的运行尽可能高效。线程调度需要考虑的因素很多,包括用户需求(例如响应时间、周转时间等)、系统需求(例如吞吐量、CPU利用率等)、线程的优先级、资源平衡等。
在实际的线程调度中,往往需要在不同的需求之间做出权衡。例如,如果我们希望系统的响应时间尽可能小,那么就需要频繁地进行线程切换;但是,线程切换会带来额外的开销,从而降低系统的吞吐量和CPU利用率。这就像我们在玩游戏和接电话之间做出选择一样,无论我们选择哪一个,都需要牺牲另一个,而且选择之后还要想想刚才进行到哪里了。
如何做出决策
线程调度的时机通常在操作系统内核对中断/异常/系统调用处理后返回到用户态时。例如:
- 当你正在使用文本编辑器写作时,突然移动了鼠标,操作系统就需要处理这个鼠标移动的中断,处理完毕后可能就会切换到其他线程,如音乐播放器线程。
- 你的浏览器线程在运行过程中出现了异常,操作系统处理完异常后,可能会切换到其他正常运行的线程,如邮件客户端线程。
- 你的下载线程请求读取硬盘文件,操作系统在处理完这个系统调用后,可能会切换到其他线程,如视频播放线程。
具体的调度算法根据操作系统的不同而不同,例如UNIX使用的是动态优先数算法,Linux使用的是抢占式算法(CFS完全公平调度算法),而Windows使用的是基于动态优先级的抢占式多任务调度。这些算法的细节这里就不赘述了,有兴趣的可以去查询一下。
总的来说,线程的sleep时间不准确主要是由于线程调度和上下文切换的开销导致的。在实际的编程中,我们需要意识到这一点,避免过度依赖sleep的准确性。
关注微/信/公/众/号:萤火架构,提升技术不迷路。