我做过好几年各种单点调度对象相关的系统,第一次听到“调度器的调度策略”这个说法,是在一个做 AI 推理平台的同事口中。当时他正在解释为什么同样的模型服务,在流量高峰期一个节点能顶住三个节点的事,最后总结成一句:“调度器写得好不好,完全看它选了什么策略,以及策略之间怎么权重,这是真的。”
后来我自己接触的东西越多越发现,从操作系统里的进程调度,到 GPU 显存分配,再到工作流平台的任务编排,甚至现在的扩散模型采样,到处都有“调度器”在起作用。但这个名词在不同语境下含义差别极大:RTOS 里的调度器靠优先级安排任务执行,vLLM 里的调度器管的是 GPU 显存和请求批处理,海豚调度器管的是 DAG 依赖,而生成图像工具里的“调度器”指的是采样步长和去噪节奏。如果抓不住背后那套“在有限时空里让资源服务好多个任务”的底层逻辑,很容易被不同包装搞得一头雾水。
这篇文章准备从一个比较实用的角度切入,把“调度器的调度策略”拆开来讲。我会结合自己的实践项目,聊到 Linux 的 CFS 到 EEVDF 的演化、大小核处理器的异构调度、RTOS 里的实时调度原理、vLLM 这类推理引擎的自研调度器、海豚调度器的 DAG 编排,以及自己写调度器时的核心决策点。内容会尽量口语化,适合各个水平的开发者看。
1. 一个队列就能说清:调度器到底在调度什么
很多人觉得调度器很玄,其实它的工作拆开来看,就三件事:排队、挑选、切换。任务来了,先放进队列;策略引擎按某种规则选出一个“下一个”;然后系统做上下文切换,把那项任务真正放到资源上运行。所有调度器,不管是操作系统里的进程调度,还是 AI 推理引擎里的显存调度,底层都是这三件事的组合。区别只在于“排队排的是什么”“挑选的依据是什么”“切换的代价多大”。
为什么要把这三件事拆开?因为调度的难点往往不在于“选谁”本身,而在于每个环节的代价完全不同。在 CPU 上,挑选一个进程通常只要 O(log n) 查找,但切换一次上下文可能要几十个时钟周期;在 GPU 推理场景,交换一个序列出显存的代价可能是几毫秒甚至更久。所以现实中优秀的调度器会尽量避免频繁切换,并且尽量在挑选环节做出正确预测,让切换代价最小化。
1.1 调度策略不是一个算法,而是一组权衡
“调度策略”这个词之所以容易让人迷惑,是因为它通常不是一个单纯算法,而是一组判定逻辑的组合。先来先服务、短作业优先、时间片轮转、优先级抢占、多级反馈队列……每一类策略本质上都是在回答同一个问题:在公平、效率、延迟、稳定性这几个目标之间,你到底要为哪个买单?
先来先服务(FCFS)实现简单、公平感强,但一旦队头有一个执行时间特别长的任务,后面所有短任务都会被堵住。时间片轮转(RR)解决了“长任务霸占”的问题,但频繁切换带来了不小的开销。短作业优先(SJF)能让平均等待时间最低,但它需要一个前提——你提前知道每个任务要跑多久,这在真实系统里几乎做不到,而且长任务容易被饿死。
所以调度策略没有完美的银弹。它就像城市交通信号灯:你想要主干道不堵,就得牺牲支路;你想所有车都走最短路径,某些路段就会超载。调度器能做的,是明确你关心什么指标,然后围绕它去配参数、选策略。理解了这个,你才会明白为什么 Linux 要用那么多参数去不断修正 CFS 的行为,为什么 vLLM 要自己重写调度器而不是直接套用操作系统那一套。
1.2 一个简单但很关键的视角:指标冲突
在做 Linux 性能调优的时候,我经常要面对一个选择:把 CPU 优先给交互式短任务,还是给批处理长任务?这两者的需求是直接冲突的。交互式任务希望延迟越低越好,最好一上来就被调度;批处理任务希望吞吐越高越好,不喜欢被频繁切换。操作系统给出的答案是动态优先级加时间片补偿:短任务执行完后快速回到队列前面,长任务则逐步降低动态优先级,把机会让出来。
这个例子说明,调度器的本质就是一台“目标加权决策机”。你不可能同时让所有等待时间最短、所有截止时间不超、所有任务都公平、吞吐又最大化。所以读调度器源码时,不要上来就翻机制,先去看它最终优化的指标是什么:是平均响应时间、尾部延迟,还是资源利用率。理解了这个,你才能判断一个调度策略适不适合你自己的场景,以及该从哪个参数开始调。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Linux CPU 调度策略的演进:从 CFS 聊到 EEVDF,再看优先级和实时调度
在服务端、嵌入式、桌面端,Linux 的 CPU 调度策略几乎算是“默认答案”。很多研发都见过 /proc/sys/kernel/sched_ 开头的参数,甚至调过 nice 值,但未必清楚背后的完整逻辑。这一节我把它拆开讲。
2.1 Linux CFS 的工作原理:公平不是“轮着来”
早期 Linux 2.4/2.5 的调度器是一种 O(1) 调度器配合优先级队列,能够处理大量任务,但在交互式和批处理任务混跑时,响应不够平滑。后来引入了完全公平调度器(CFS),核心思想彻底换了个方向:不再搞静态时间片,而是引入“虚拟运行时间”(vruntime)。
CFS 把所有可运行任务放在一棵按 vruntime 排序的红黑树里,每次调度总是选择 vruntime 最小的那个任务执行。任务每运行一阵,它的 vruntime 就会增加;nice 值不同的任务,vruntime 的增长速度也不同——nice 值越小(优先级越高),vruntime 增长越慢,于是它更容易排在树的前面。这样设计的好处是,调度器的选择标准和结果都可以用“每个任务累计获得了多少 CPU”来度量,从机制上保证了长期公平。
我在实操中的体会是,CFS 的“公平”是相对公平,不是严格时间片等分。一个 nice 0 的进程和一个 nice 19 的进程同时跑,前者获得的 CPU 份额往往是后者的数倍。如果你想让某个任务在后台不影响主业务,调 nice 往往比改业务逻辑有效得多。但注意,nice 影响的是权重,不是硬抢占,高 nice 任务的延迟仍然可能不可控。
