Thread.Sleep(1) 会导致高频的上下文切换,高风险引爆CPU

今天在关注很久的博主"一线码农聊技术"发布了一篇 记一次 .NET 某药厂业务系统 CPU爆高分析,详细拜读了他的分析,感觉这个场景也挺常见的,于是就进行了阅读了一些资料和进行了一些思考。于是有了这篇内容,我也来分享下。

线程调度器作用机制

C# 线程调度器是 .NET Framework 提供的一种机制,用于管理应用程序中运行的线程。线程调度器负责分配 CPU 时间片给不同的线程,并确保它们按照预期的顺序执行。它还负责监视和调整线程的优先级,以及在需要时暂停或恢复线程的执行。线程调度器的有以下特征:

  1. 线程优先级:线程调度器允许您设置每个线程的优先级。这决定了线程在竞争CPU资源时的优先级。
  2. 抢占式:C# 线程调度器是抢占式的,这意味着高优先级的线程可以中断正在运行的低优先级线程,以便获得更多的 CPU 时间。
  3. 非确定性:由于线程调度器的存在,线程的执行时间是不确定的。这意味着一个线程可能需要等待另一个线程完成其工作,然后才能开始自己的工作。
  4. 可重入性:线程调度器支持可重入代码。这是因为线程可以在任何时候被中断和恢复。
  5. 定时器:线程调度器还提供了一个定时器机制,允许您安排代码在指定的时间间隔内运行。

结合上面的特征,我们可以知道。在多线程环境下,操作系统的线程调度器负责决定哪个线程可以获得 CPU 时间执行任务。线程调度器以时间片(通常为几十毫秒)为单位进行调度,每个线程在自己的时间片内执行任务,然后被挂起,让其他线程执行。

Thread.Sleep(1) 的影响

当一个线程调用 Thread.Sleep(1) 方法时,该线程会被挂起一毫秒,并让出 CPU 时间。在这一毫秒内,操作系统可能会选择重新调度其他线程来执行。然而,当 Thread.Sleep(1) 方法返回时,操作系统可能会立即重新调度原始线程,这样就会发生上下文切换。

还可能引发其他的问题:

  1. 延迟不准确:Thread.Sleep(1)并不能保证精确地暂停1毫秒,它仅表示至少暂停1毫秒。实际上,操作系统可能会将线程挂起更长的时间,这可能导致延迟不准确。
  2. 资源浪费:使用Thread.Sleep(1)会使线程进入睡眠状态,并释放CPU资源。然而,如果频繁地使用Thread.Sleep(1),则可能导致CPU资源的浪费,因为线程被唤醒后可能立即再次进入睡眠状态。
  3. 响应性下降:如果在关键路径上使用Thread.Sleep(1),那么可能会导致应用程序的响应性下降。特别是在实时应用程序或需要快速响应用户输入的场景下,较长的睡眠时间可能会导致用户感觉到延迟。
  4. 线程过多:如果在多线程应用程序中的每个线程都使用Thread.Sleep(1),那么可能会导致系统同时运行过多的线程,从而增加了调度和上下文切换的开销。频繁的上下文切换会导致性能下降和系统负担增加。即使 Thread.Sleep(1) 只挂起了一毫秒,但由于时间片调度机制的存在,操作系统可能会不停地在不同线程之间进行上下文切换,消耗大量的 CPU 时间。
  5. 不可移植性:Thread.Sleep(1)的行为在不同的操作系统和硬件上可能会有所不同。在某些平台上,最小的可用睡眠时间可能大于1毫秒,因此使用Thread.Sleep(1)可能不会产生预期的效果。

示例说明

假设有两个线程 A 和 B,它们轮流执行,当线程 A 执行到 Thread.Sleep(1) 时,它会挂起一毫秒。此时,操作系统可能会选择调度线程 B 来执行。然而,当线程 A 的挂起时间结束后,操作系统可能会又立即重新调度线程 A,导致频繁的上下文切换。

csharp 复制代码
using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread threadA = new Thread(RunA);
        Thread threadB = new Thread(RunB);

        threadA.Start();
        threadB.Start();

        threadA.Join();
        threadB.Join();
    }

    static void RunA()
    {
        while (true)
        {
            Console.WriteLine("A is running");
            Thread.Sleep(1);
        }
    }

    static void RunB()
    {
        while (true)
        {
            Console.WriteLine("B is running");
            Thread.Sleep(1);
        }
    }
}

解决方案

为了避免高频的上下文切换,可以考虑使用更长的等待时间,例如使用 Thread.Sleep(10) 等方法来减少上下文切换的次数。另外,也可以考虑使用其他等待机制,如 ManualResetEvent、Monitor.Wait 或 async/await 结合 Task.Delay 等,这些方法可以更加精确地控制等待时间,并减少上下文切换的次数。

相关推荐
止语Lab2 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活2 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困2 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn2 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530142 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908402 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金2 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛2 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构
IT_陈寒3 小时前
Vue的响应式让我加班到凌晨3点,原来问题出在这
前端·人工智能·后端
卷无止境3 小时前
提升 Python 代码健壮性的方法大盘点
后端·python