2. 后台调度基本概念:任务、线程、进程、CPU时间片、调度周期
做帧率抖动排查,绕不开调度这一关。说白了,后台调度就像是操作系统的交通警察——谁该用CPU、用多久、什么时候换人,都由它说了算。我在项目里见过太多开发同学只盯着业务代码,结果帧率一抖就懵了。其实只要搞清楚几个基本概念,80%的调度问题都能定位到根因。
2.1 任务(Task)——最小的调度单元
任务,是调度器眼中的“一个活”。它可以是一个线程,也可以是一个进程,甚至是一个轻量级的协程。我个人习惯把任务看作“一段需要CPU执行的代码路径”。
举个例子:你的渲染管线里有一个纹理上传操作,这就是一个任务。如果这个任务跑得太久,后面所有的绘制帧都得等着。嗯,这里要注意:任务的执行时间直接决定了帧率的上限。我在优化一款手机游戏时,发现每帧有一个2ms的纹理压缩任务,当时以为是GPU瓶颈……后来用Trace一看,其实是CPU上串行执行的解码任务占了时间片。把那个任务拆分到另一个线程后,帧率立刻从30fps跳到了45fps。
2.2 进程(Process)——资源隔离的“容器”
进程是操作系统分配资源的最小单位。每个进程有自己的地址空间、文件描述符、信号表等等。你想想看,如果两个进程互相乱写内存,系统早就崩了。所以进程之间是天然隔离的。
但隔离有代价——进程间通信(IPC)很慢。我在做一个音视频同步项目时,音频进程和视频进程通过共享内存通信,结果每次同步都要等几十个微秒。后来我把它们合并到一个进程的不同线程里,帧率抖动直接消失了。所以对于高实时性的后台调度场景,建议尽量少跨进程,多用线程。
2.3 线程(Thread)——真正的CPU执行体
线程是进程内的执行流,共享进程的地址空间。说白了,线程才是真正被调度器放到CPU上跑的东西。一个进程可以有一堆线程,但同一时刻只能有一个线程在某个CPU核心上运行。
为什么帧率抖动会和线程数有关?我见过最经典的案例:一个8核CPU上开了32个后台线程,每个线程都在轮询某个状态。结果调度器疲于切换,实际干活的渲染线程拿不到时间片,帧率像心电图一样上下跳。解决方案很简单——用线程池控制并发数,把活跃线程压到CPU核心数左右。
CPU核心数 - 1(留一个给UI线程)。
2.4 CPU时间片(Time Slice)——一次能跑多久
时间片,就是调度器分配给一个线程的连续CPU使用时长。常见的桌面系统时间片在4ms~20ms之间,移动端一般在2ms~6ms。为什么?因为移动设备的散热和续航更敏感,时间片太短会导致大量切换开销,太长又会让其他线程饿死。
你可能会问:帧率抖动和时间片有什么关系?关系大了!如果你的渲染线程每次只拿到一个时间片(比如5ms),但渲染一帧需要8ms,那么它必须等到下一次调度才能继续。这个“等”的过程就是帧率抖动的直接原因。我记得有一次排查某个直播App的掉帧,发现后台有一个日志刷盘线程每隔4ms就唤醒一次,每次只写几KB数据——但它硬生生抢走了渲染线程的时间片。把日志线程改成了每200ms批量写入,帧率立刻稳定了。
| 系统类型 | 典型时间片范围 | 对帧率的影响 |
|---|---|---|
| 桌面Windows/Linux | 8ms ~ 20ms | 帧率抖动较大,常见于后台任务抢占了长时间片 |
| 移动Android/iOS | 2ms ~ 6ms | 时间片更短,调度更频繁,但切换开销也更明显 |
| 实时系统(RTOS) | 0.1ms ~ 1ms | 时间片极短,优先级抢占严格,适合硬实时渲染 |
2.5 调度周期(Scheduling Period)——调度的“心跳”
调度周期,是调度器刷新进程/线程优先级、重新分配时间片的时间间隔。你可以把它想象成系统的一个时钟节拍。Linux的调度周期默认是 6ms 到 10ms(取决于内核版本和 CONFIG_HZ 配置)。
为什么调度周期和帧率抖动有关?因为如果你的渲染帧率是60fps(每帧16.6ms),而调度周期是10ms,那么一帧里可能发生一次或两次调度事件。如果调度事件刚好发生在渲染的中间——比如顶点提交刚一半就被切走了——那这一帧就会延迟,造成掉帧。我曾经在排查一个AR应用时发现,每隔3帧就会掉一帧,时间戳显示正好落在调度周期的时间点上。最后通过绑核(将渲染线程固定在某个CPU核心,并设置RT优先级)解决了问题。
• 不要盲调优先级,先通过
sched_attr 查看当前调度策略和周期• 如果必须用实时优先级,配合设置
sched_rr_interval 为一个较大的值(比如100ms)来减少切换频率
小结:五者的关系
这五个概念就像一个倒金字塔:用户任务在最上层,操作系统把它映射到一个或多个进程;进程包含线程,线程是真正跑在CPU上的东西;调度器为每个线程分配时间片,并在每个调度周期决定下一轮谁跑。用一张表总结:
| 概念 | 角色 | 对帧率抖动的直接作用 |
|---|---|---|
| 任务 | 业务逻辑的载体 | 任务太长会撑满时间片,导致后续帧延迟 |
| 进程 | 资源隔离、地址空间独立 | 跨进程通信会引入额外延迟,增加抖动概率 |
| 线程 | CPU调度的主体 | 线程数过多导致上下文切换频繁,压缩渲染线程时间 |
| 时间片 | 一次连续执行的最大时长 | 时间片小于帧渲染耗时,必须等下次调度才能继续 |
| 调度周期 | 系统重新排班的时间间隔 | 调度周期若与帧周期重叠,可能产生周期性掉帧 |
搞清楚这五个概念,你再看帧率抖动的时候,脑子里就会自动浮现出这张关系图:到底是任务太大?线程太多?时间片不够?还是调度周期撞上了渲染时间?接下来几章,我会带你一步步把这些疑点变成确定性的证据。