ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

嵌入式任务调度:从裸机大循环到RTOS优先级抢占与CFS对比

嵌入式任务调度:从裸机大循环到RTOS优先级抢占与CFS对比 1. 嵌入式任务调度到底在解决什么问题1.1 从裸机大循环到多任务并发的必然演进刚入行做单片机开发那会儿我写的代码基本都是一个大循环包打天下。主函数里放一个while(1)里面依次调用按键扫描、串口收发、数码管刷新、传感器读取每个函数跑一遍循环往复。这种结构在功能单一的场景下确实够用代码直观调试也简单出了问题打断点一步步跟就行。但项目一旦复杂起来这套玩法很快就撑不住了。最典型的问题是实时性没法保证如果某个函数执行时间过长比如等待一个ADC转换完成或者处理一帧较长的串口数据后面所有任务的响应都会被推迟。按键按下去要等几十毫秒才有反应串口数据来了来不及接收直接丢包这种问题在裸机架构下几乎无解因为你没法在一个线性执行的流程里同时照顾到所有任务的时效要求。于是很自然地就会想到能不能让多个任务看起来“同时”在跑每个任务只负责一件事谁紧急谁先执行不紧急的往后排。这就是任务调度要解决的核心问题——在单个CPU上通过合理的切换策略让多个任务分时复用处理器资源同时保证关键任务的响应时间可控。嵌入式操作系统RTOS就是在这个需求下诞生的而任务调度器则是整个RTOS最核心的部件没有之一。1.2 调度器的本质在有限资源下做取舍很多人刚接触RTOS的时候会把调度器想得很神秘觉得里面有什么高深算法。其实剥开来看调度器干的事情非常朴素维护一个就绪任务列表每次需要切换的时候从列表里挑一个最该跑的任务把CPU交给它。就这么简单。但“最该跑”这三个字大有文章。什么叫最该跑是优先级最高的是等了最久的还是剩余时间片最少的不同的判断标准就衍生出了不同的调度算法。而且嵌入式场景还有自己的特殊性内存通常只有几十KB到几MBCPU主频可能只有几十MHz没有MMU内存管理单元中断响应要求通常在微秒级。这些约束决定了嵌入式调度器不能照搬桌面操作系统的做法必须在功能和开销之间找到平衡点。我见过不少初学者一上来就啃Linux的CFS调度器源码结果被红黑树、虚拟运行时间这些概念绕得云里雾里回头再看uC/OS-II的调度代码反而觉得太简单不像操作系统。其实这两者面向的场景完全不同复杂度差异是合理的。理解这一点后面的内容就好展开了。2. 主流嵌入式调度方案的核心思路拆解2.1 uC/OS的优先级抢占式调度简单直接但有效uC/OS现在叫Micrium OS但大家还是习惯叫uC/OS在嵌入式圈子的地位不用多说很多人的第一个RTOS就是它。它的调度器设计思路非常清晰每个任务分配一个唯一的优先级优先级数值越小优先级越高调度器永远选择就绪态中优先级最高的任务运行。如果某个更高优先级的任务就绪了立刻抢占当前任务这就是“抢占式”的含义。具体实现上uC/OS用了一个非常巧妙的数据结构来加速查找。它维护一个OSRdyTbl[]数组作为就绪表每个比特位对应一个优先级1表示就绪0表示未就绪。同时还有一个OSRdyGrp变量每8个优先级为一组只要组内有任务就绪对应位就置1。查找最高优先级任务的时候先查OSRdyGrp确定在哪一组再查该组的OSRdyTbl确定具体是哪一位两次查表就能定位时间复杂度是O(1)。这个设计在uC/OS-II里最多支持64个优先级8组×8位到了uC/OS-III扩展到了256个但核心思路没变。这种调度方式的优点是确定性极强。任何时刻最高优先级任务只要就绪就一定能拿到CPU响应时间有严格上界。对于硬实时系统来说这一点至关重要。但缺点也很明显低优先级任务可能永远得不到执行机会也就是所谓的“饥饿”问题。如果高优先级任务一直不阻塞低优先级任务就永远在就绪表里躺着。实际项目中通常需要靠任务主动延时或等待信号量来让出CPU这对开发者的设计能力有一定要求。2.2 Linux CFS调度追求公平的完全不同的哲学如果说uC/OS的调度哲学是“强者通吃”那Linux的CFSCompletely Fair Scheduler就是“人人有份”。CFS从2.6.23内核开始成为默认调度器它的核心思想是给每个任务维护一个“虚拟运行时间”vruntime每次调度选择vruntime最小的任务运行。任务运行的时候vruntime会增长但增长速度和任务的优先级nice值相关——优先级高的任务vruntime增长慢优先级低的任务增长快。这样既保证了公平性又体现了优先级差异。CFS用红黑树来组织就绪任务key就是vruntime。红黑树是一种自平衡二叉搜索树查找、插入、删除的时间复杂度都是O(log n)。每次调度取红黑树最左边的节点也就是vruntime最小的任务。任务运行一段时间后vruntime增加如果不再是最小值就被重新插入到树中合适的位置。这个“一段时间”就是调度周期CFS会根据就绪任务数量动态调整保证每个任务在周期内至少运行一次。CFS的设计目标不是硬实时而是吞吐量和公平性的平衡。它适合通用计算场景比如服务器、桌面环境这些场景下任务数量多、类型杂很难为每个任务静态分配优先级。但在嵌入式实时场景下CFS的公平性反而可能成为问题一个低优先级的日志任务和一个高优先级的控制任务如果vruntime相近日志任务可能会抢占控制任务的执行时间这在硬实时系统里是不可接受的。所以嵌入式Linux通常需要配合RT补丁PREEMPT_RT或者使用双内核方案如Xenomai来满足实时性要求。2.3 两种方案的适用场景对比对比维度uC/OS优先级抢占调度Linux CFS调度调度依据静态优先级动态vruntime时间复杂度O(1)O(log n)实时性硬实时响应时间有上界软实时需RT补丁增强公平性不保证高优先级可饿死低优先级强保证每个任务都有运行机会内存开销极小几KB级别较大需要MMU支持典型应用MCU、DSP、工业控制应用处理器、网关、HMI任务数量通常不超过几十个可支持成百上千个选哪个不是看哪个更“高级”而是看场景需求。一个电机控制项目任务数量少、实时性要求苛刻uC/OS是更合适的选择。一个智能家居网关需要同时跑网络协议栈、UI渲染、数据存储任务数量多且优先级关系复杂嵌入式Linux更合适。我个人的经验是如果项目里有人提出“能不能跑个Python脚本”或者“需要接USB摄像头”那基本就可以考虑上Linux了如果项目要求“中断响应不超过5微秒”那还是老老实实用RTOS。3. 任务调度核心机制的深度解析3.1 任务状态机理解调度的基础不管哪种RTOS任务都不是一直处于可运行状态的。一个任务从创建到删除会在几个状态之间迁移理解这些状态是理解调度的前提。以uC/OS为例任务主要有五种状态休眠态Dormant、就绪态Ready、运行态Running、等待态Waiting、中断服务态ISR。休眠态就是任务代码已经写好但还没被创建或者已经被删除了。调用OSTaskCreate()之后任务进入就绪态被放入就绪表。调度器选中它之后进入运行态开始执行代码。如果运行过程中调用了OSTimeDly()或者等待信号量就进入等待态从就绪表中移除。等待的条件满足后延时到期或信号量可用重新回到就绪态。中断发生时如果中断服务程序唤醒了某个更高优先级任务中断返回时会触发调度当前任务被换出高优先级任务进入运行态。这个状态机看起来简单但实际调试中很多问题都出在状态迁移上。比如一个任务在等待信号量时被删除了信号量却还被其他任务释放就可能出现悬空指针。再比如中断服务程序里调用了会导致阻塞的API任务状态机就乱了。这些坑后面会详细说。3.2 上下文切换调度器的“最后一公里”调度器决定切换任务之后真正完成切换动作的是上下文切换代码。所谓上下文就是任务运行时CPU寄存器的值包括程序计数器、堆栈指针、通用寄存器、状态寄存器等。切换的时候先把当前任务的寄存器值保存到它的任务控制块TCB或堆栈中再从下一个任务的TCB或堆栈中恢复寄存器值最后跳转到新任务的执行点。这个过程听起来简单但有几个关键点容易出问题。第一是保存的完整性有些寄存器在C函数调用规范里是“调用者保存”的有些是“被调用者保存”的上下文切换代码必须把所有可能被修改的寄存器都保存下来漏一个就可能导致任务行为异常。第二是堆栈对齐ARM Cortex-M系列要求堆栈8字节对齐如果切换时堆栈指针没对齐浮点运算或者某些库函数可能直接HardFault。第三是中断嵌套如果在中断服务程序里触发调度必须确保中断嵌套深度正确处理否则可能破坏中断返回地址。我实测过一个案例在Cortex-M3上移植uC/OS任务切换偶尔会跑飞。查了很久发现是PendSV异常处理程序里没有正确处理FPU寄存器的保存虽然M3没有FPU但代码里误用了M4的移植文件。换成正确的移植文件后问题消失。这个教训说明上下文切换代码必须和具体内核架构严格匹配不能想当然地复用。3.3 调度时机什么时候该切、什么时候不该切调度器不是随时随地都在运行的它只在特定时机被触发。这些时机主要包括任务主动让出CPU调用延时或等待API、中断服务程序退出、任务优先级被动态修改、时间片用完如果是时间片轮转调度。其中中断退出时的调度是最关键的。中断服务程序通常会释放信号量或发送消息唤醒等待中的任务。中断处理完成后系统需要判断是否有更高优先级任务就绪如果有就触发切换。这个过程在uC/OS里叫“中断级任务切换”在Linux里叫“中断返回调度”。实现方式通常是在中断退出前设置一个挂起标志然后在中断返回指令执行前检查这个标志如果需要切换就跳转到调度器。这里有个常见的误区很多初学者以为中断处理越短越好于是在中断里只置个标志把实际处理放到任务里做。这个思路本身没错但如果标志置位后没有正确触发调度高优先级任务可能要到下一个时间片才能运行实时性就丢了。正确的做法是在中断退出前调用RTOS提供的调度触发API比如uC/OS的OSIntExit()它会自动判断是否需要切换。4. 从零搭建一个可运行的任务调度框架4.1 环境准备与基础数据结构定义要真正理解调度器光看代码不够最好自己动手写一个简化版。下面我以Cortex-M内核为例用C语言和少量汇编搭建一个支持优先级抢占的迷你调度器。这个调度器不具备完整RTOS的功能但足以展示核心原理。首先定义任务控制块TCB和就绪表。TCB里至少要有堆栈指针、任务入口、优先级、状态这几个字段。就绪表用一个32位变量表示每一位对应一个优先级最多支持32个任务。#define MAX_TASKS 32 #define STACK_SIZE 256 typedef enum { TASK_READY, TASK_RUNNING, TASK_BLOCKED } task_state_t; typedef struct { uint32_t *stack_ptr; // 保存任务堆栈指针 void (*entry)(void); // 任务入口函数 uint8_t priority; // 优先级0最高 task_state_t state; // 任务状态 uint32_t stack[STACK_SIZE]; // 任务私有堆栈 } tcb_t; static tcb_t tasks[MAX_TASKS]; static uint32_t ready_bitmap 0; // 就绪位图 static tcb_t *current_task NULL;这里ready_bitmap的每一位对应一个优先级置1表示该优先级有任务就绪。查找最高优先级任务只需要找到最低位的1可以用__builtin_ctz()GCC内置函数或者ARM的CLZ指令实现效率很高。4.2 任务创建与堆栈初始化创建任务的时候最关键的是初始化堆栈让任务第一次被调度时能正确“返回”到入口函数。Cortex-M的堆栈在异常返回时会自动恢复8个寄存器R0-R3、R12、LR、PC、xPSR所以我们需要在堆栈里预先布置好这些值。int task_create(void (*entry)(void), uint8_t priority) { if (priority MAX_TASKS) return -1; tcb_t *t tasks[priority]; t-entry entry; t-priority priority; t-state TASK_READY; // 堆栈从高地址向低地址生长 uint32_t *sp t-stack[STACK_SIZE - 1]; // 布置异常返回时的寄存器值 *(--sp) 0x01000000; // xPSRThumb位置1 *(--sp) (uint32_t)entry; // PC任务入口 *(--sp) 0xFFFFFFFD; // LR返回后使用PSP *(--sp) 0; // R12 *(--sp) 0; // R3 *(--sp) 0; // R2 *(--sp) 0; // R1 *(--sp) 0; // R0 // 再保存R4-R11手动保存的部分 for (int i 0; i 8; i) { *(--sp) 0; } t-stack_ptr sp; ready_bitmap | (1 priority); return 0; }这段代码里0xFFFFFFFD是Cortex-M的EXC_RETURN值表示异常返回后使用进程堆栈指针PSP并且返回Thread模式。如果这个值设错了任务第一次运行就会直接HardFault。我当初调试的时候就因为写成了0xFFFFFFF9使用MSP导致所有任务共用主堆栈跑几个任务就栈溢出了。4.3 调度器核心与PendSV切换实现调度器本身很简单就是找到最高优先级就绪任务如果和当前任务不同就触发切换。触发切换的方式是设置PendSV异常挂起位PendSV会在所有其他异常处理完成后执行是专门为上下文切换设计的异常。void schedule(void) { if (ready_bitmap 0) return; // 没有就绪任务 uint8_t next_prio __builtin_ctz(ready_bitmap); tcb_t *next tasks[next_prio]; if (next ! current_task) { // 触发PendSV异常 *(volatile uint32_t *)0xE000ED04 (1 28); } }PendSV的处理程序需要用汇编写因为要手动保存和恢复寄存器。核心逻辑是先判断当前是否有运行中的任务有就保存其堆栈指针然后加载下一个任务的堆栈指针最后恢复寄存器并异常返回。PendSV_Handler: MRS R0, PSP ; 获取当前任务堆栈指针 CBZ R0, PendSV_NoSave ; 如果为0说明是第一次调度 STMDB R0!, {R4-R11} ; 保存R4-R11 LDR R1, current_task ; 保存堆栈指针到TCB LDR R2, [R1] STR R0, [R2] PendSV_NoSave: LDR R0, next_task ; 获取下一个任务TCB LDR R1, [R0] LDR R0, [R1] ; 加载堆栈指针 LDMIA R0!, {R4-R11} ; 恢复R4-R11 MSR PSP, R0 ; 更新PSP ORR LR, LR, #0x04 ; 确保异常返回使用PSP BX LR这段汇编里STMDB R0!, {R4-R11}是递减堆栈并保存LDMIA R0!, {R4-R11}是递增加载并恢复。注意Cortex-M的堆栈是满递减栈所以用DBDecrement Before和IAIncrement After配对。如果搞反了保存和恢复的地址就对不上任务切换后寄存器值全乱。4.4 实测验证与性能评估把上面的代码整合到一个工程里创建三个任务任务A每100ms翻转一个GPIO任务B每200ms通过串口打印一条消息任务C每500ms更新一次LCD显示。用逻辑分析仪抓GPIO波形用串口助手看打印时间戳实测下来任务切换的抖动在2微秒以内完全满足一般工业控制的需求。如果想进一步评估调度器的开销可以在PendSV处理程序入口和出口各翻转一个IO用示波器测量高电平持续时间。在STM32F10372MHz上这个时间大约是1.2微秒包括保存和恢复所有寄存器以及堆栈操作。这个开销在大多数场景下是可以接受的但如果任务切换过于频繁比如每毫秒切换几十次累积开销就会影响CPU的有效利用率。这时候就需要考虑优化比如减少不必要的切换、合并短任务、或者提高时间片长度。5. 调度器实战中的典型问题与排查方法5.1 优先级反转现象、原因与解决方案优先级反转是RTOS开发中最经典的坑没有之一。现象是一个高优先级任务突然被阻塞了很长时间导致系统响应超时但查代码又找不到明显的死循环或长延时。根本原因是有个低优先级任务持有高优先级任务需要的资源比如互斥锁而中间又有个中优先级任务一直在运行导致低优先级任务拿不到CPU来释放资源。举个例子任务H高优先级等待互斥锁M任务L低优先级持有M任务M中优先级正在运行且不释放CPU。任务L因为优先级低于任务M一直得不到调度也就无法释放M任务H就一直等。结果就是任务H被任务M间接阻塞了而任务M的优先级其实比H低。解决方案通常有两种优先级继承和优先级天花板。优先级继承是指当低优先级任务持有高优先级任务需要的锁时临时把低优先级任务的优先级提升到和高优先级任务相同等释放锁后再恢复。uC/OS的互斥量Mutex就支持优先级继承创建的时候把OSMutexPrioInherit选项打开即可。优先级天花板则是给每个互斥量设定一个天花板优先级任何获取该锁的任务都会被提升到天花板优先级。两种方案各有适用场景优先级继承更常用但实现复杂度稍高。5.2 栈溢出最隐蔽的杀手栈溢出是另一个高频问题而且往往表现为随机崩溃极难定位。每个任务都有自己独立的堆栈如果任务里定义了大的局部数组、递归调用层数过深、或者中断处理使用了任务堆栈都可能溢出。溢出后可能覆盖相邻任务的TCB或堆栈导致完全不相干的代码出错。排查栈溢出的方法有几个。最直接的是在任务堆栈的末尾填充特定模式比如0xDEADBEEF定期检查这些位置是否被改写。uC/OS提供了OSTaskStkChk()函数可以统计任务堆栈的使用量返回空闲栈空间大小。我通常会在调试阶段把栈检查放在空闲任务里每隔一秒打印一次各任务的栈使用情况一旦发现某个任务剩余栈空间低于20%就重点排查。预防方面首先要合理估算栈大小。一个经验公式是栈大小 函数调用深度 × 每层栈帧大小 中断嵌套最大深度 × 中断栈帧大小 安全余量。Cortex-M的中断栈帧是32字节8个寄存器×4字节如果中断里还有函数调用还要加上调用链的栈消耗。安全余量一般留30%到50%。其次要避免在任务里定义大数组需要大缓冲区就用全局变量或动态分配。最后如果编译器支持开启栈保护选项如GCC的-fstack-protector能在溢出时触发异常而不是静默破坏内存。5.3 调度延迟从理论到实测的差距理论分析调度延迟的时候我们通常假设中断响应是即时的、调度器是O(1)的、上下文切换是固定开销的。但实测下来从事件发生到高优先级任务开始执行中间可能有好几微秒甚至几十微秒的延迟。这些延迟来自多个环节中断响应延迟CPU完成当前指令、保存现场、中断处理时间如果中断服务程序较长、调度器执行时间、上下文切换时间。要降低调度延迟可以从几个方面入手。第一缩短中断服务程序只做最必要的操作把耗时处理放到任务里。第二提高中断优先级确保关键中断能及时响应。第三减少任务切换频率避免不必要的调度。第四如果CPU支持使用尾链Tail-Chaining优化连续的中断处理可以省去重复的现场保存恢复。第五在Cortex-M上把PendSV和SysTick的优先级设为最低确保它们不会阻塞其他中断。我实测过一个电机控制项目最初调度延迟有15微秒后来把电流环中断的优先级提到最高把通信中断的优先级降低延迟降到了3微秒以内。这个优化对电机控制的平滑性提升非常明显低速运转时的转矩脉动小了很多。5.4 常见问题速查表问题现象可能原因排查方法解决方案系统随机死机栈溢出填充模式检查、OSTaskStkChk()增大栈、减少局部变量高优先级任务响应慢优先级反转检查互斥量使用启用优先级继承任务切换后跑飞上下文保存不完整检查汇编保存寄存器列表补全寄存器保存低优先级任务不执行被高优先级饿死查看任务状态增加延时或等待中断中调用阻塞API状态机混乱检查ISR代码只发信号不阻塞时间片轮转不均匀时间片设置不当测量各任务运行时间调整时间片长度系统启动后第一个任务不运行堆栈初始化错误检查EXC_RETURN值修正为0xFFFFFFFD6. 调度策略选型与进阶优化思路6.1 根据项目需求选择调度算法选调度算法不是选最先进的而是选最合适的。我一般从几个维度来评估任务数量、实时性要求、任务间依赖关系、开发团队经验。任务数量少10个以内、实时性要求高硬实时、任务优先级关系明确的项目优先级抢占式调度是最佳选择。uC/OS、FreeRTOS、RT-Thread都支持这种模式代码成熟度高社区资源丰富。任务数量多几十个以上、实时性要求相对宽松、任务类型多样的项目可以考虑时间片轮转或者多级队列调度。Linux的CFS适合任务数量多且优先级动态变化的场景但需要评估实时性是否满足。如果项目里既有硬实时任务又有非实时任务可以考虑混合方案用RTOS跑硬实时任务用Linux跑非实时任务两者通过共享内存或消息队列通信。这种架构在工业网关、机器人控制器里很常见。选型的时候还要考虑团队的技术栈如果团队都是单片机背景强行上Linux可能反而增加项目风险。6.2 调度器性能优化的几个实用技巧第一个技巧是减少不必要的调度。每次调度都有开销如果任务切换过于频繁CPU大量时间花在保存恢复现场上。可以通过合并短任务、增大时间片、使用事件驱动代替轮询来降低切换频率。我见过一个项目串口接收任务每收到一个字节就切换一次波特率115200的时候每秒切换上万次CPU利用率直接飙到80%。后来改成DMA接收加空闲中断一次处理一帧数据切换频率降到每秒几十次CPU利用率降到5%以下。第二个技巧是合理设置任务优先级。优先级分配要遵循“速率单调”原则周期越短、实时性要求越高的任务优先级越高。同时要避免优先级过于集中留出足够的优先级空间给后续可能增加的任务。我通常会把优先级分成几个区间0-10给硬实时任务11-20给软实时任务21-30给非实时任务31给空闲任务。第三个技巧是利用硬件特性。Cortex-M的PendSV异常专门用于上下文切换不会阻塞其他中断。Cortex-M7等高端内核还有缓存和TCM紧耦合内存把调度器代码和任务堆栈放在TCM里可以显著降低访问延迟。如果CPU支持双堆栈MSP和PSP确保任务使用PSP中断使用MSP可以避免任务栈溢出影响中断处理。6.3 从单核到多核调度器的新挑战现在多核MCU越来越常见比如STM32H7的双核版本、ESP32的双核、树莓派RP2040的双核。多核调度比单核复杂得多核心问题是如何在多个核之间分配任务以及如何保证共享资源的一致性。最简单的多核调度是AMP非对称多处理每个核跑独立的RTOS实例任务静态绑定到某个核核间通过共享内存或消息队列通信。这种方式实现简单但负载不均衡一个核忙死另一个核闲死。更复杂的是SMP对称多处理所有核共享一个就绪队列调度器把任务分配到空闲的核上。SMP需要处理核间同步、缓存一致性、自旋锁等问题实现难度大很多。我个人的建议是如果项目对算力要求不是特别苛刻优先考虑AMP方案把不同功能模块分配到不同核上比如一个核跑控制算法一个核跑通信协议栈。这样既能利用多核性能又避免了SMP的复杂性。如果确实需要SMP建议使用成熟的RTOS比如FreeRTOS的SMP版本或者Zephyr不要自己从头实现。6.4 调试工具与手段推荐调试调度问题光靠打印和断点往往不够需要一些专门的工具。硬件方面逻辑分析仪和示波器是必备的可以抓GPIO波形来观察任务执行时间和切换时机。如果MCU支持ETM嵌入式跟踪宏单元或SWO单线输出可以用Tracealyzer或者Percepio这样的工具做可视化跟踪能看到每个任务的运行区间、切换原因、中断时序非常直观。软件方面RTOS通常提供一些钩子函数Hook可以在任务切换、任务创建、栈溢出等事件发生时调用自定义代码。利用这些钩子可以记录调度历史出问题的时候回溯。我习惯在任务切换钩子里记录时间戳和任务ID到一个环形缓冲区系统崩溃后通过调试器读取缓冲区内容就能还原崩溃前的调度序列。如果条件允许在开发阶段开启所有断言Assert和参数检查。uC/OS的OS_DEBUG_EN、FreeRTOS的configASSERT都能在参数非法时立即停机避免问题扩散。这些检查在发布版本里可以关掉以节省开销但调试阶段一定要开。7. 一些踩坑后的个人体会调度器这东西看代码觉得简单用起来才知道坑多。我最大的体会是不要迷信“优先级越高越好”。很多初学者把所有任务都设成高优先级结果就是高优先级任务互相抢占系统反而更乱。优先级应该反映任务的实时性需求而不是重要性。一个不紧急但重要的任务优先级可以低一些只要保证它最终能执行就行。另一个体会是调度器的行为高度依赖任务的设计。如果任务里到处是阻塞调用、长延时、忙等待再好的调度器也救不了。我见过一个项目任务里用while(!flag);等标志位直接把CPU占满其他任务全饿死。后来改成信号量等待问题立刻解决。所以与其花时间调调度参数不如先把任务设计好。最后不要过度设计。有些项目明明只有三五个任务非要上复杂的调度算法结果调试成本远超收益。简单场景用简单方案把省下来的时间花在业务逻辑和测试上项目成功率反而更高。调度器是工具不是目的能满足需求的就是好调度器。
返回列表