news 2026/9/4 22:38:34

Work-Stealing 调度器:本地队列与全局队列的工作窃取机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Work-Stealing 调度器:本地队列与全局队列的工作窃取机制

Work-Stealing 调度器:本地队列与全局队列的工作窃取机制

在多核高并发系统中,如何将海量的微小异步任务(Task)均匀分发到各个 CPU 物理核心上,是决定异步运行时吞吐上限的核心难题。

如果采用最简单的**单全局共享队列(Single Global Queue)**架构:所有 Worker 线程在每次获取新任务时,都必须去抢占全局队列的互斥锁。当并发任务量达到数十万时,多核心之间的缓存一致性协议(MESI 广播)与锁争用,会直接把 CPU 算力全部消耗在自旋与总线锁上。

为了解决这一吞吐瓶颈,现代高性能异步运行时(如 Tokio、Go Runtime、Java ForkJoinPool)无一例外地采用了工作窃取算法(Work-Stealing Scheduling)

理解 Tokio 中本地环形队列(Local Run Queue)、全局注射队列(Global Injection Queue)与窃取机制的协同设计,是排查高负载下任务调度倾斜与毛刺的关键。

+--------------------------------------------------------------------------+ | Tokio Work-Stealing 调度拓扑 | +--------------------------------------------------------------------------+ | 全局注射队列 (Global Injection Queue / MPMC 慢速保底) | | [ Task G1 ] -> [ Task G2 ] -> [ Task G3 ] ... | +--------------------------------------------------------------------------+ ^ ^ | 周期性轮询 (如每 61 次迭代) | 队列溢出溢出回退 v v +-----------------------------+ +-----------------------------+ | Worker 0 (CPU 核心 0) | 窃取 | Worker 1 (CPU 核心 1) | | 本地无锁环形队列 (Local 256) | <==== | 本地无锁环形队列 (Local 256) | | [T1] [T2] [T3] ... | 一半 | [ 空闲空转中... ] | +-----------------------------+ +-----------------------------+

1. 本地队列设计:无锁单生产者-多消费者环形缓冲区

Tokio 为每个 Worker 线程分配了一个固定长度(默认 256 槽位)的本地运行队列(Local Run Queue)

这个本地队列具有独特的并发访问模式:

  • 本地 Worker 线程是唯一的生产者与主消费者:本地 Worker 从队列头部(Head)取任务执行,并将新生成的本地 Task 压入队列尾部(Tail)。在这个独占路径上,操作完全不需要加锁,通过无锁的原子游标更新即可在几个纳秒内完成;
  • 其他空闲 Worker 是并发的偷取者(Stealers):当其他 Worker 线程自身队列变空时,它们会扮演消费者从该队列中并发“偷取”任务。

巧妙的 256 容量限制与溢出处理

为什么本地队列的长度被固定为 256,而不是无限制动态扩容的数组?

  1. 防止单个 Worker 发生内存饥饿与倾斜:如果一个任务内部疯狂递归生成子任务,256 的容量上限能防止这些子任务全部积压在单个 Worker 核心上;
  2. 溢出回退到全局队列:当本地队列被填满 256 个任务时,Worker 会将本地队列中**一半的任务(128 个)打包一次性转移到全局注射队列(Global Queue)**中,主动让出给其他 Worker 分担。

2. 工作窃取流程:半数批量窃取(Steal Half)

当 Worker A 发现自己的本地队列变空时,它不会立刻陷入休眠(Sleep),而是进入窃取状态机:

  1. 随机挑选受害者(Victim Selection):为了避免所有空闲 Worker 同时盯上同一个繁忙 Worker 造成二次争锁,Worker A 会随机选择一个目标 Worker B;
  2. 批量窃取一半任务(Steal Half)
    Worker A 不会只偷 1 个任务,而是通过 CAS 操作尝试一次性从 Worker B 的队列头部偷取其当前积攒任务总数的50%(最多 128 个),并直接搬迁到自己的本地队列中;
  3. 消除颠簸(Anti-Thrashing):一次性偷取一半任务,保证了 Worker A 在接下来的几百微秒内拥有充足的工作储备,不需要频繁触发昂贵的跨核心窃取。
// 伪代码:工作窃取核心逻辑 impl Worker { pub fn fetch_next_task(&mut self) -> Option<Task> { // 1. 优先消费本地最高优先级的 LifoSlot if let Some(task) = self.lifo_slot.take() { return Some(task); } // 2. 从本地无锁队列中弹出任务 if let Some(task) = self.local_queue.pop() { return Some(task); } // 3. 周期性(每 61 次迭代)主动检查全局队列,防止全局任务被饿死 if self.tick % 61 == 0 { if let Some(task) = self.global_queue.pop() { return Some(task); } } // 4. 本地为空,进入跨核心窃取逻辑 self.steal_from_peers() } fn steal_from_peers(&mut self) -> Option<Task> { let victims = self.get_randomized_peers(); for peer in victims { // 尝试从 peer 队列偷取一半任务 if let Some(stolen_task) = peer.local_queue.steal_half_into(&mut self.local_queue) { return Some(stolen_task); } } // 5. 最后兜底:检查全局队列 self.global_queue.pop() } }

3. LIFO Slot 优化:极致的 CPU 缓存局部性

Tokio 还有一个极其精妙的微架构优化——LIFO Slot(后入先出单槽位)

当一个正在执行的 Task A 通过tokio::spawn产生了一个新 Task B 时,调度器不会把 Task B 扔进队列尾部,而是将其暂存在一个名为lifo_slot的单任务寄存器式变量中。

当 Task A 执行完当前的这一轮 Poll 后,Worker 线程会优先取出lifo_slot中的 Task B 立即执行

为什么这样做能大幅降低延迟?

因为 Task B 刚刚被 Task A 创建,Task B 所需的数据(包括请求结构体、张量切片)在 CPU 的 L1/L2 Cache 中处于完全温热(Hot Cache)状态!立即执行 Task B 可以实现极致的 CPU 缓存命中,避免了将任务推入队列尾部再由其他核心冷加载读取导致的 Cache Miss。

通过本地无锁环形队列、批量偷取一半策略以及 LIFO Cache 局部性插槽的三位一体配合,Work-Stealing 架构在多核心硬件上构建起了一套既能各自狂飙、又能自动动态削峰填谷的终极调度秩序。

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

AI视频API降价下,应用团队的成本模型与供应商适配策略

先不讨论“贴钱 1 折甩卖 Seedance 2.5”这个标题描述是不是精确&#xff0c;也不去预判 Libtv 们和上游模型厂商的长期关系。站在做应用、做产品、做内容的团队视角&#xff0c;这类信号真正值得拆解的是另一个问题&#xff1a;当 AI 视频生成 API 突然大幅降价&#xff0c;下…

作者头像 李华
网站建设 2026/9/4 22:33:00

DeepSeek V4 Flash九家服务商延迟对比:测试方法与选型指南

DeepSeek V4 Flash 0731 这版模型最近有一轮很值得看的横向对比&#xff0c;主题是九家服务商的 latency。这类测试为什么值得盯&#xff1f;因为把同一个模型换成不同服务商后&#xff0c;首字延迟和生成速度可能差得比模型切换还明显。适合看的读者有两类&#xff1a;一类是要…

作者头像 李华
网站建设 2026/9/4 22:23:05

基于51单片机与Proteus的货车侧翻检测系统仿真全流程解析

简介&#xff1a;本资源是一套面向嵌入式初学者与课程设计者的51单片机实践项目&#xff0c;聚焦货车侧翻风险实时监测这一典型安全应用场景。系统以Proteus仿真为核心&#xff0c;通过滑动变阻器模拟车身两侧高度差&#xff0c;实现倾斜度阈值可设、超限自动报警与模拟刹车功能…

作者头像 李华
网站建设 2026/9/4 22:18:14

AI Agent 四根支柱拆解:LLM、工具、记忆、规划怎么协同 原创

AI Agent 四根支柱拆解&#xff1a;LLM、工具、记忆、规划怎么协同 "让 AI 自动帮我干活"喊了两年&#xff0c;多数人搭出来的 Agent 仍然是&#xff1a;一次 API 调用&#xff0c;一次输出&#xff0c;然后卡住。原因多半不在模型不够强&#xff0c;而在架构缺了东西…

作者头像 李华
网站建设 2026/9/4 22:15:37

硬件人的拼豆:从零散模块到可交付系统的工程化开发路径

“拼豆”这个词&#xff0c;最近在硬件圈被拿来讨论。我第一次看到“硬件人的拼豆”这个说法&#xff0c;第一反应是自己桌上那堆开发板、核心板、传感器模块和转接板——它们确实像拼豆&#xff1a;单个看起来不起眼&#xff0c;但只要底板正确、引脚对应、供电到位&#xff0…

作者头像 李华
网站建设 2026/9/4 22:14:15

录屏卡顿音画不同步?录前清理后台是关键

录屏30分钟&#xff0c;前3分钟正常&#xff0c;第12分钟开始画面卡顿&#xff0c;第20分钟声音和画面对不上&#xff0c;最后软件提示“资源不足”直接退出。这种场景你一定不陌生。很多人遇到这种情况&#xff0c;第一反应是骂录屏软件不行&#xff0c;然后换软件、换版本、换…

作者头像 李华