news 2026/10/13 4:22:17

CPU调度实战解析:从CFS公平调度到上下文切换优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CPU调度实战解析:从CFS公平调度到上下文切换优化

做系统方面的开发这么多年,CPU调度这块的知识,说实在的,是那种“天天见,但未必真懂”的硬骨头。尤其是当你从应用层往下钻,开始跟多线程、高并发、实时性打交道时,会发现程序跑得快不快,很多时候不是你代码写得够不够好,而是操作系统愿不愿意把CPU这件“最贵的资源”分给你。这篇东西,我不打算按教科书那套章节目录来讲,而是从我实际接触过的项目、踩过的坑、调优过的参数出发,把CPU(处理器)调度这件事彻底掰开揉碎。看完之后,你至少能明白操作系统到底是怎么决定“下一秒让谁跑”的,也能在工作里遇到CPU占用异常、响应延迟、吞吐上不去这类问题时,有清晰的排查思路。

1. CPU调度到底是什么——先把问题问清楚

1.1 调度的本质:CPU时间给谁用

随便打开一台Linux服务器,运行着的进程可能上百个,但CPU核心就那么几个。假设你只有一颗核心,却有一百个任务都想跑,这时候谁先跑、谁后跑、每个跑多久、跑完之后的现场怎么维护——这一整套决策和执行机制,就是CPU调度。不用想得太玄,它本质上就是一门“排队经济学”。

但这里头有个容易混淆的点:CPU调度调度的是“进程”,还是“线程”?这取决于操作系统的设计。现代操作系统,比如Linux,调度的基本单位其实是线程(或者说可运行实体),不过进程内部通常至少有一个主线程,所以从宏观上看调度进程和调度线程是一回事。我在某嵌入式项目里就吃过亏,当时以为只要进程里开了多线程就能“并行”跑,结果发现单核CPU上线程再多也只是轮着来,所谓“并行”根本不存在,只是调度器把时间片切得足够细,让你感觉像同时在跑。

所以,调度器要解决的核心问题就一句话:CPU时间有限,任务需求无限,怎么分配才能让系统整体表现最优。“最优”这个词在不同场景下标准完全不同,这就是为什么世界上没有一种调度算法能通吃所有场景。

1.2 调度器面对的三大矛盾

调度器在设计时必须同时考虑三个互相打架的指标,这也是后面所有调度算法的出发点。

第一个是响应时间。你在终端敲一条命令,按下回车,到屏幕上出现结果,这个延迟就是响应时间。交互式系统(比如桌面系统、在线游戏服务器)极度依赖响应时间,如果你敲个键等两秒才有反应,用户早就疯了。

第二个是吞吐量。单位时间内系统能完成的任务总数。跑批处理任务时,比如大数据离线计算、视频转码服务,系统不需要跟人类交互,核心追求就是“同样一小时,能跑完多少活”。

第三个是公平性。每个任务都能分到CPU时间,避免某些任务饿死。听起来容易,做起来难——公平和效率天然冲突,就像一百个顾客排队买东西,如果为了绝对公平,每个人只能买一包纸巾然后轮到下一个,那买大件商品的顾客就会被拆得七零八落,总体的“成交额”反而下降。

这三者的矛盾,决定了调度算法要么偏向响应性,要么偏向吞吐量,要么在中间找平衡。Linux的CFS调度器、Windows的调度器、以及各类实时操作系统的调度器,本质上都是在这三个维度上做取舍。

1.3 调度发生的技术前提

调度不会平白无故发生,必须有事件触发系统“检查”当前该不该换人。常见的触发时机有三类:进程状态转换(比如一个进程从阻塞变成就绪)、中断产生(比如网卡收到数据包,打断当前进程执行)、时间片耗尽(当前进程用完了分给它的CPU时间)。

知道这个有什么好处?排查问题很有用。比如你发现一个程序响应很慢,但CPU占用率并不高,这时候就要怀疑是不是进程阻塞太久,导致调度器根本“看不见”它。反过来说,如果一个进程CPU占用率极高,但其他进程卡成狗,多半是调度器的公平机制被某种因素破坏了(后面会讲优先级反转、CPU亲和性这些坑)。所以,理解调度时机,等于拿到了系统卡顿问题的第一把钥匙。

2. 调度算法拆解——每种选择都是一种妥协

2.1 先来先服务(FCFS)与短作业优先(SJF):两个极端

先来先服务是最朴素的算法,谁先到达就谁先执行,除非自己阻塞,否则CPU一直让它占用。它好理解,实现也简单,但问题极其明显:如果一个长任务先到了,后面所有短任务都得排队等它,响应时间瞬间爆炸。这就是所谓的护航效应——一艘大船在前面慢悠悠开,后面一队快船全部被压住。

短作业优先则反过来,谁的预计执行时间短,谁就优先执行。理论证明它能让平均等待时间最小化,但问题也致命:你怎么知道一个任务要跑多久?这需要预知未来,实际根本做不到。任务没跑完之前,它的“预计执行时间”只能靠历史推测,而且如果短任务源源不断到达,长任务会一直被插队,最终导致饿死。

我在实际项目里基本不会直接用这两个极端算法,但它们很重要,因为后面所有改进都建立在对这两个“病人”的诊断上。比如时间片轮转解决的是FCFS的响应问题,多级反馈队列想解决的则是SJF的饿死问题。

2.2 时间片轮转(RR):公平但不够聪明

时间片轮转的思路非常简洁:所有就绪进程排成一个队列,每个进程分到一个固定长度的时间片,时间片用完就排到队尾,轮到下一个。这是典型的“人人有份”策略,交互式响应能得到保障,因为最多等一个队列长度的时间就能轮到自己。

但时间片设置多少是个学问。拍脑袋想想,时间片越短,响应越快,但调度切换本身有成本(保存现场、载入新现场,这需要CPU时间),切换太频繁,CPU光忙着“换人”了,真正干活的占比就低了。时间片太长,又退化成了FCFS,交互响应变差。我做过一次简单实验,同一台机器上跑同样的混合负载,时间片从100ms降到10ms,响应时间改善了约40%,但总吞吐量下降了约15%。这就是活生生的取舍。

实际系统里很少有纯RR算法了,但RR的思路渗透在几乎所有现代调度器中。它的最大贡献在于引入了“时间片”这个基础概念,而且让人意识到:调度的核心对象其实是时间,不是任务。

2.3 优先级调度:让重要的先走,但小心饿死和反转

优先级调度给每个任务打一个优先级,就绪队列按优先级排序,高优先级的先跑。它解决的是任务“重要性”的差异问题——比如实时监控进程比后台日志进程重要得多。

但优先级调度有两个著名的大坑。第一个是饿死:低优先级任务可能永远轮不到CPU。解决办法是老化(Aging),低优先级任务每等待一段时间就自动抬高优先级,直到最终有机会执行。第二个是优先级反转(后面第5章展开讲):高优先级任务反而被低优先级任务卡住,比如高优先级任务在等一个低优先级任务持有的锁,而中优先级任务又抢占了低优先级任务的CPU,导致高优先级任务“间接”被中优先级压制。

真实的调度器一般会把优先级分成多个等级队列,属于同一优先级的任务内部用RR轮转,不同优先级之间用强制抢占。这就演化出了下一层结构——多级队列。

2.4 多级反馈队列(MLFQ):现代调度器的经典底座

多级反馈队列是我认为最优雅的调度设计之一,全靠“动态调整优先级”这个机制。它把就绪队列分成多级,优先级从高到低排列。新任务进来时放在最高优先级队列,分给一个短时间片执行。如果任务在时间片内执行完了,说明它是短任务,维持或降低优先级;如果时间片耗尽还没执行完,说明它可能是长任务,就把它降到更低一级的队列,同时分配更长的时间片。

妙就妙在这个“反馈”机制:系统不需要提前知道任务是长还是短,它通过任务的实时表现自动分类。短任务在高优先级队列很快就被执行完,响应极好;长任务慢慢沉到底层,用大时间片批量执行,吞吐也不算差。在这套机制下,I/O密集型任务天然占便宜——它们经常由于阻塞而主动让出CPU,下一次唤醒时仍然放在高位,所以交互响应很快。CPU密集型任务则不断降级,拿到的时间片一次比一次长。

这套机制玩透了,就能解释很多线上现象。比如我调优过一个服务,进程的CPU消耗其实不高,但客户端总是反馈超时。用监控工具一看,进程优先级确实高,但调度器把大量I/O密集的辅助线程也放在了同一优先级队列,它们频繁唤醒抢占,高优先级反而被稀释。这个思路后来成了我排查性能问题的“起手式”。

2.5 Linux CFS:基于“虚拟时间”的公平调度器

现代主流操作系统很少直接实现教科书上的MLFQ,但都把它的思想吸收进化了。我重点说Linux,因为Linux的CFS(Completely Fair Scheduler)是现在默认的调度器,也是我做大多数性能分析时绕不开的东西。

CFS和传统MLFQ最大的区别,是它不维护“优先队列数组”,而是维护一棵红黑树,按每个进程的虚拟运行时间排序。虚拟运行时间是一个加权值,和进程优先级相关:优先级越高(nice值越小),运行相同物理时间累积的虚拟时间越少,于是在红黑树里的位置越靠左,越容易被选中执行。每次调度,CFS直接选虚拟时间最小的进程运行。它保证的是“在相同优先级的前提下,每个进程得到的CPU份额完全公平”,如果某个进程实际运行多了,它的虚拟时间涨得快,自然被排到后面去。

这里有一个关键实操点:CFS追求的不是让每个进程“平均轮流跑”,而是让每个进程获得“公平的CPU份额”。这意味着,如果你有2个核心,同时跑2个CPU密集的进程,它们各自得到约100%的单核。如果同时跑4个,则每个进程约各占50%。你可以通过nice值调整单个进程的权重。nice值的范围通常是-20到19,默认0。nice每差1,权重大约差1.25倍左右。

实操中我经常用chrt和nice命令做调度调试。比如有个后台任务,希望它“有点CPU就行,不要抢主业务”,直接nice -n 15 ./background_task就能压低它的优先级。反过来,某个核心服务优先级不够,可以用chrt -r -p 98 PID把它变成实时进程,优先级直接拉满。但这个操作极度危险,不要在生产环境乱用实时优先级,一旦某个进程变成SCHED_FIFO且内部有死循环,整个系统都可能被它锁死,连shell都救不回来。

3. 调度是怎么发生的——从触发到切换的完整链路

3.1 调度触发路径:中断返回、系统调用返回、主动阻塞

调度器不是一条独立运行的“线程”,它附着在系统的某些执行路径上。理解调度是怎么发生的,必须理解这些路径。

最常见的一条路径是中断返回。CPU在处理时钟中断时(通常每1ms到4ms一次),中断处理程序会标记当前进程的时间片是否耗尽。如果耗尽了,中断返回前会调用调度器,选一个新进程执行。这也是为什么时间片调度能周期发生补不依赖于进程自觉。

第二条路径是系统调用返回。当进程调用sleep()、wait()、mutex_lock()这类阻塞型系统调用时,它主动告诉内核自己暂时无法继续运行,内核会把它放进等待队列,然后调度器立刻重新选一个进程出来执行。

第三条路径是进程被唤醒。比如网络上来了一个数据包,内核发现了等待这个数据的进程,它会把这个进程从等待队列移到就绪队列,并且根据优先级决定是否要“抢占”当前正在运行的进程。被唤醒的进程优先级高于当前进程时,调度器会强制切换——这叫作抢占式调度。

我调试过一个奇怪的问题:一个服务在高峰期吞吐掉了一半,CPU使用率却正常。用perf抓了一下事件分布,发现问题出在某个锁的竞争上——大量进程不是在工作,而是阻塞在等待锁上,唤醒后互相抢占,调度器把大量时间花在切换上了。所以判断性能瓶颈时,CPU使用率高低都不直接代表系统健康度,要看“指令实际执行”的比例。

3.2 调度器的工作内容:从选择进程到切换上下文

调度器拿到CPU控制权后,做的事情可以拆成四步。

第一步,从就绪队列中选一个“最佳候选人”。CFS会从红黑树最左叶子节点取出虚拟时间最小的进程;实时调度器则从实时队列中按优先级取第一个。

第二步,计算该进程还剩下多少时间片。如果它是第一次运行,会分配一个默认时间片(CFS里时间片不是固定值,而是根据进程权重与系统负载动态计算)。时间片到了系统负载调整时会重新分配。

第三步,保存当前进程的上下文。所有寄存器值都要存进当前进程的内核栈或进程控制块里。这里包括指令指针、栈指针、通用寄存器、浮点寄存器状态,以及内存映射信息(页表基址),必要时还要处理TLB刷新。

第四步,加载目标进程的上下文。从目标进程的控制块里恢复寄存器和堆栈,刷新页表基址,然后跳转到目标进程上次中断或阻塞的位置继续执行。

整个流程在软件层面就是几百行内核代码,但它的成本主要花在硬件行为上——比如TLB刷新、cache失效。这也是为什么频繁切换进程会导致性能下降,本质是CPU缓存被打断。

3.3 上下文切换成本:比你想象的高得多

学术界对上下文切换成本没有一个统一数字,因为它极度依赖硬件架构和应用特征。但我实测过一个参考数值:在常见的x86_64平台上,一次线程上下文切换大概需要消耗1到5微秒的CPU时间,如果频繁触发,比如每秒几十万次切换,CPU的有效利用率就会损失几个百分点,甚至更高。

这里有个容易忽略的点:切换成本不只是切换本身,还包含cache和TLB的“冷启动”代价。新进程的代码和数据不在当前CPU的L1/L2缓存里,它开始执行时,前几百条指令几乎都要访问内存加载,这段时间比切换本身更拖慢性能。

所以,从应用优化的角度看,减少线程上下文切换远比优化指令执行次数更有效。我们公司某个高并发服务,压测时发现线程数从200提到2000,吞吐量不升反降。用pidstat -w一看,每秒上下文切换次数超过了30万次。后来把线程池大小限制在核心数的两倍左右,问题直接消失。从那以后,我调优的第一步就是看上下文切换率,不看CPU利用率。

3.4 实操:用硬件计数器观察调度引起的cache抖动

分享一个我常用的排查手段。用perf stat观察上下文切换和cache miss的联动关系:

perf stat -e context-switches,cache-misses,cache-references,instructions,cycles -p <PID> sleep 10

如果发现context-switches数值很高,且cache-misses/L1-dcache-load-misses比例异常高,基本就能判定调度切换导致了cache不停刷新。接下来可以看具体是哪些线程在频繁切换:

pidstat -w -t -p <PID> 1

输出中cswch/s表示每秒自愿上下文切换数,nvcswch/s是非自愿上下文切换数。非自愿切换高,说明线程频繁被抢占,多半是线程数量过多或优先级设置不当。自愿切换高,说明线程在频繁阻塞和唤醒,多半是锁竞争、I/O等待。

4. 多核与实时场景下的调度进阶

4.1 多核负载均衡:让每个核心雨露均沾

单核时代调度器只需要管“下一个进程是谁”。多核时代则多了一维:这个进程跑在哪个核心上。如果几个核心忙得冒烟,另外几个核心却空闲,系统整体利用率就会被拉低,这就是负载不均衡。

系统的负载均衡机制主要做两类事:周期性负载均衡和新任务唤醒时负载均衡。周期性均衡是每过一段时间(比如每millisecond级别)检查各核心的就绪队列长度,把长队列里的任务迁移到短队列。新任务唤醒时均衡则是当某个进程被唤醒时,调度器会判断当前核心是否合适,如果不合适就找一个更空闲的核心放上去。

但这里有个需要权衡的技术点:迁移进程本身有成本——进程的cache、TLB在旧核心上的状态全丢了,迁到新核心一切要重新热起来。所以有些场景下,保持“不均衡”反而更好。这也是为什么现代调度器设计了CPU亲和性和cpuset机制,允许你手动人为划分进程归属。

4.2 CPU亲和性:把进程和核心绑死的利与弊

CPU亲和性(sched_setaffinity)允许你把进程限定在某个或某几个CPU核心上运行。好处很明显:进程一直在同样的核心上跑,cache命中率最高,线程间同步的开销也小。坏处也很明显:如果绑定的核心过载了,其他空闲核心也帮不上忙,系统整体吞吐受限。

我做过一个高吞吐网络转发项目,把数据面进程绑定在专门的两个核心上,控制面管理在另外两个核心上,彻底物理隔离。效果立竿见影,丢包率从千分级降到了万分级以下。但要注意,这种绑定方式极依赖日常流量的规律性,一旦流量模式变化导致数据处理压力不均,反而会引入瓶颈。

实操时可以通过taskset命令快速验证绑定效果。例如把某个进程绑到核心0和核心1:

taskset -c 0,1 ./your_app

实时调整正在运行的进程,也可以:

taskset -cp 0,1 <PID>

设置之后用taskset -cp <PID>查看当前绑定情况。

4.3 实时调度策略:当“尽快”不够,需要“一定”

普通分时系统追求的是统计意义上的公平和平均延迟,但实时系统要求的是确定性——一个任务必须在截止时间前完成,不能“大概在1毫秒内完成,偶尔拖到10毫秒也能接受”。

Linux提供两套实时调度策略:SCHED_FIFO和SCHED_RR。前者是先进先出实时策略,相同优先级的实时线程按FCFS执行,不会被同优先级的新线程抢占,除非它自己阻塞或让出CPU。后者是实时时间片轮转,同优先级的实时线程会按时间片轮转。

用chrt可以查看和设置实时优先级:

# 查看进程当前的调度策略和优先级 chrt -p <PID> # 设置成SCHED_FIFO,优先级80 chrt -f -p 80 <PID> # 设置成SCHED_RR,优先级85 chrt -r -p 85 <PID>

需要注意实时优先级范围是1到99,数字越大优先级越高。一旦设置成实时调度,该进程就比所有普通进程优先级都高,包括内核的多数后台服务线程。这是双刃剑:它在关键时刻保证了你的业务线程不被抢占,但一旦写了一个死循环,系统将彻底卡死。我见过线上事故,有人把监控脚本设置成了SCHED_FIFO,结果脚本里有个bug造成无限循环,整个服务器连ssh都连不上,最终只能物理重启。实时调度策略必须配死循环保护、看门狗之类的机制才能上生产。

此外,实时任务还有个隐藏问题:频繁让出CPU的实时任务,其实会吃掉整个核心的“剩余时间”。因为高实时优先级意味着它总能插队,普通进程永远排在后面,这会导致该核心上其他所有非实时进程完全饿死。所以除非系统核心数足够充裕(比如绑核给实时任务单独用),否则不要轻易把业务全面实时化。

5. 常见调度问题与排查技巧实录

5.1 优先级反转:经典到必须手写一遍的坑

有一个经典问题场景:三个进程,A是高优先级实时进程,B是中优先级普通进程,C是低优先级进程。A和C共享一个锁,执行情况是:A先跑,拿到锁,然后被C打断了?不对,重新捋一下——更典型的场景是C先拿到锁,然后A想拿锁,但锁还在C手上,于是A被阻塞。接着B来了,B的优先级(高)虽然不如A,但高于C,B抢占C运行。此时A明明优先级最高,却因为C没释放锁而被B压制,等于A间接等待B的完成——优先级反转就发生了。

正确解法是优先级继承和优先级天花板。优先级继承是当高优先级任务A被低优先级任务C持有锁阻塞时,系统把C的优先级临时提升到A的级别,让它尽快跑完释放锁。优先级天花板则是预先给锁关联一个最高优先级,任何持锁的任务运行优先级都不低于天花板,从根源上避免反转。

Linux的rt_mutex就实现了优先级继承机制,所以默认mutex在遇上实时线程时相对安全。但如果你用的是自旋锁或其他自定义同步机制,就可能踩坑。实操建议:实时任务之间的锁交互,必须选用支持优先级继承的锁原语,并且用MessageQueue之类的无锁队列来规避这个问题。

5.2 调度延迟:为什么CPU闲着,任务还是慢

有时候CPU使用率很低,但任务延迟超出预期。这时候要看调度延迟——即任务从就绪到真正被CPU执行的时间间隔。如果用perf sched record抓一段数据,然后perf sched latency查看,可以很直观看到每个进程在就绪队列里等了多久。

一个常见原因:内核里某个持锁时间长的路径阻塞了调度器。比如文件系统锁竞争激烈时,所有任务都卡在等待锁上,CPU虽然看着空闲,但进不去临界区。另一个原因是cgroup的CPU限流:如果你用cgroup限制了某个服务的CPU配额,比如100ms周期里最多只能用30ms,那么即使整个机器空闲,你的服务跑满30ms后也会被强制暂停来凑周期。这种问题cpu.stat里看得清清楚楚,throttled_time如果不为零,说明发生了限流。

排查思路上,我会先查系统整体调度延迟分布,筛出延迟异常高的进程,再确认它是在等锁、限流、还是被更高的优先级任务抢占。总之,延迟高不一定意味着CPU不够,要先确认调度器有没有正常“放行”。

5.3 吞吐量上不去的隐藏元凶:太长的运行队列

很多人看系统繁忙与否,只盯着load average。但它其实是个一维数字,看不出各核心运行队列长度分布。我在某次性能优化中发现,系统load average只有4(4核机器),但业务吞吐就是上不去。用vmstat一看,r列(运行队列长度)持续在8以上。说明CPU实际上早已过载,只不过很多任务短暂阻塞在I/O上,load average没体现出来而已。

更极端的案例是超线程导致核心“竞争激烈”:一个物理核心的两个逻辑CPU共享执行单元,如果两个逻辑CPU都在跑CPU密集任务,整体的有效吞吐可能只有其中一个独占时的1.3倍左右。所以高密度部署时,盲目填满逻辑CPU并不能线性提升吞吐,需要在真实业务负载下压测出最佳并发度。

优化手段无非两类:减少任务数(合并任务、去重轮询循环)和增加每任务单次执行的效率(提高cache命中率、减少锁粒度)。而不是简单堆积线程数。

5.4 调度相关排查命令速查

这部分干货比较零散,但非常实用,我直接在表格里整理:

排查目标命令核心输出
运行队列长度vmstat 1r列(运行队列)
上下文切换频率pidstat -w -t 1cswch/s、nvcswch/s
调度延迟事件perf sched record+perf sched latency各任务延迟分布
调度器策略/优先级chrt -p PIDpolicy、priority
CPU亲和性taskset -cp PID允许运行的CPU列表
CPU限流状态cat /sys/fs/cgroup/cpu.statthrottled_time、nr_periods
各核心软中断分布cat /proc/softirqs软中断在各核计数

5.5 一个典型排查实录:一次诡异的CPU分配不均

分享一个印象深刻的案例。某次线上服务反馈“部分实例处理能力明显弱于其他实例”。几个实例跑的是同样的容器,同样的代码,CPU核数也一样,理论上吞吐应该一致。用mpstat -P ALL 1查看各核使用率,发现一个奇怪的现象:某个实例占用的两个核心都已经接近100%,但其他核心闲着。业务上并没有任何绑定设置,为什么任务总往固定两个核心挤?

顺着线索查,发现这两个核心上承载了大量网络软中断(irqbalance失效了或者是网络队列绑定没生效)。网络中断固定走这两个核,导致数据包相关进程还有与之有依赖的worker线程也集中在这两个核上。后续优化方向就变成调整网络队列CPU亲和性,配合irqbalance策略让中断均匀化,问题才解决。

这个案例提醒我:调度器分配CPU时不是凭空算概率,中断、锁、cgroup、亲和性这些机制随时会打破它的“公平想象”。排查问题时不要只看调度器本身,要把整个CPU周边机制一起看。

6. 一些更底层的复盘与扩展思路

做CPU调度方向的优化,做到后面会发现一个残酷事实:用户态的代码再优化,也绕不开内核调度策略给你设的天花板。这不一定意味着你必须去改内核,但至少需要知道自己“能控制”和“控制不了”的边界在哪里。

能控制的包括:进程/线程的优先级(nice值)、调度策略(SCHED_OTHER、SCHED_FIFO、SCHED_RR)、CPU亲和性、cgroup的CPU配额和权重。控制不了的主要是:内核中断处理线程的调度、内存管理引起的周期性操作、其他租户(容器)带来的CPU争抢。所以,上线前压测很重要——不同负载模式、不同并发度、不同优先级组合下,调度行为差异巨大。

如果是做嵌入式实时方向的,我建议一定要把调度器和中断的关系搞清楚。实时系统里很多延迟不是调度器原因,而是中断屏蔽时间过长导致的。关中断时间长,时钟中断来不了,调度器就无法获得执行机会,任务再高优先级也得等着。

如果做的是云原生方向,cgroup的CPU管理就是一个新的“调度分层”。容器里看到的CPU使用率,往往是已经被cgroup限流修饰过的结果。你看到的100%可能只是配额内的100%,不是整机的100%。之前有个项目,服务在K8s里配置了1个CPU的limit,但实际业务需要1.5核,结果响应越来越慢,CPU监控还是顶着100%。后面查cgroup限流,才发现大量的throttled周期在那堆积。这算是调度问题在容器场景下的新皮肤。

最后分享一个我自己的习惯:遇到任何性能问题,第一步永远是先区分“CPU饥饿”和“CPU闲置但工作积压”。前者看运行队列和上下文切换,后者看锁竞争、阻塞分布和队列长度。调度知识看起来是操作系统的纯理论,但实际上它每天都能变成定位线上问题的利器。把上面这些机制吃透,比死记任何调参技巧都管用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 4:22:05

短租开放数据全流程分析:数据清洗到房价预测建模

做数据分析最怕什么&#xff1f;不是不会调库&#xff0c;而是拿到一堆真实数据后不知道从哪下手。我建议第一个完整项目就从某短租平台的开放数据开始&#xff1a;这是某国际都市的房源数据&#xff0c;字段覆盖价格、房型、位置、评论时间、可订天数&#xff0c;既有连续数值…

作者头像 李华
网站建设 2026/10/13 4:21:40

Scrum框架底层逻辑拆解:从经验主义到角色制衡的敏捷实践

很多团队在引入Scrum框架的时候&#xff0c;都没有认真问过一句&#xff1a;这套框架的底层逻辑到底是什么&#xff1f;日程表上排满了Sprint Planning、Daily Scrum、Review、Retrospective&#xff0c;看板上贴满便签&#xff0c;用户故事也按要求写了“作为XX&#xff0c;我…

作者头像 李华
网站建设 2026/10/13 4:21:31

Agent Skills实战:从通用大模型到垂直领域技能系统

1. 从“什么都能聊”到“什么都能干”&#xff1a;Agent Skills 到底在解决什么问题通用大模型刚出来那阵子&#xff0c;大家的兴奋点都集中在“它居然能跟我对话”这件事上。你问它天气、让它写首诗、帮你想个公司名字&#xff0c;它都能接得住。但真把它丢进一个具体业务场景…

作者头像 李华
网站建设 2026/10/13 4:21:08

莫烦DQN代码精讲:287行读懂强化学习核心原理

1. 项目概述&#xff1a;为什么“莫烦DQN代码”至今仍是强化学习入门绕不开的锚点如果你刚接触强化学习&#xff0c;大概率会在某个深夜的搜索框里敲下“DQN 入门”“Q网络怎么写”“PyTorch DQN 示例”&#xff0c;然后被一条标题为《莫烦Python&#xff1a;DQN》的教程反复推…

作者头像 李华
网站建设 2026/10/13 4:21:04

轻量级本地任务调度系统ZTMS实战指南

1. 项目概述&#xff1a;这不是一个“咸鱼”项目&#xff0c;而是一套轻量级任务调度系统的实操落地“咸鱼ZTMS实例—程序”这个标题乍看有点让人摸不着头脑——“咸鱼”是平台名&#xff1f;代号&#xff1f;还是某种自嘲式命名习惯&#xff1f;其实它背后指向的&#xff0c;是…

作者头像 李华
网站建设 2026/10/13 4:20:24

手机打造电影感台词拼图:质感、光影与排版全攻略

朋友圈里的那种台词拼图&#xff0c;看着是真有电影味儿。黑底白字&#xff0c;配上一句恰到好处的台词&#xff0c;再加一点光影和噪点&#xff0c;整个画面的情绪感一下就上来了。不少人以为这种图得用电脑软件精修&#xff0c;或者需要专业的设计基础&#xff0c;其实真不是…

作者头像 李华