工作窃取是一种并行任务调度策略:每个工作线程维护自己的双端任务队列,空闲线程从别人队列尾部窃取任务,从而自动实现负载均衡。它源自 MIT 的 Cilk 项目,如今是 Java ForkJoinPool、Go 调度器与 Rust Rayon 的核心机制。

| 外文名 | Work Stealing |
| 理论奠基 | 布卢莫夫、雷瑟森(Cilk 项目) |
| 核心结构 | 每线程双端队列 |
| 解决问题 | 并行任务负载均衡 |
| 代表实现 | ForkJoinPool、Go 调度器、Rayon |
工作窃取(Work Stealing)是一种用于多线程并行计算的动态任务调度算法。其思想是让每个工作线程优先执行自己产生的任务,只有在无事可做时才去其他线程的队列里“偷”任务,以极低的协调开销实现负载均衡。该策略随麻省理工学院的 Cilk 并行语言项目而系统化,罗伯特·布卢莫夫(Robert Blumofe)与查尔斯·雷瑟森(Charles Leiserson)在上世纪九十年代给出了严格的理论分析,证明其执行时间与空间开销均接近最优。
并行程序把计算切成大量小任务后,如何把任务分给线程是关键:集中式全局队列会成为争用热点,静态平均分配又无法适应任务耗时不均。工作窃取让调度去中心化:忙的线程互不打扰,闲的线程主动找活干,通信只在失衡时发生,因此扩展性极好。
工作窃取已是并行运行时的事实标准:Java 的 ForkJoinPool(JDK7 引入,支撑并行流)、Go 语言运行时的 goroutine 调度器、Rust 的 Rayon 数据并行库、微软 .NET 的任务并行库 TPL、英特尔 TBB、异步运行时 Tokio 等都采用这一机制。它尤其适合分治递归产生的不规则任务负载,如并行排序、树与图遍历、光线追踪等。
问:为什么从队列两端分别操作?答:所有者按后进先出执行,数据还热在缓存里;窃取者按先进先出拿最老最大的任务,减少窃取频率,两端分离也天然降低了并发冲突。
问:任务切多细才合适?答:任务过细则调度开销占比升高,过粗则难以均衡;常用做法是设阈值,小于阈值的子问题直接串行执行,如并行排序在小数组上退化为插入排序。
问:工作窃取适合所有并行程序吗?答:不,它面向大量动态生成的独立小任务;对流水线型、有严格顺序依赖或实时性要求极高的负载,专用调度策略可能更合适。

| 外文名 | Work Stealing |
| 理论奠基 | 布卢莫夫、雷瑟森(Cilk 项目) |
| 核心结构 | 每线程双端队列 |
| 解决问题 | 并行任务负载均衡 |
| 代表实现 | ForkJoinPool、Go 调度器、Rayon |
登录 后参与讨论
暂无讨论,来发表第一条评论吧