FEATURED · 精选文章

RIOT 多线程调度实战:用 thread_duel 示例深入理解 Round Robin 调度与线程公平性

发布时间 / 2026/9/19 23:29:37
来源 / 创域科博编辑部
栏目 / 资讯中心
RIOT 多线程调度实战:用 thread_duel 示例深入理解 Round Robin 调度与线程公平性 RIOT 多线程调度实战用 thread_duel 示例深入理解 Round Robin 调度与线程公平性【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT本文以 RIOT OS 仓库中的examples/advanced/thread_duel示例为主体系统讲解在无法使用协作式或优先级调度时如何通过可选的sched_round_robin模块实现同优先级线程的公平调度。你将掌握四种线程休息策略nice_wait、bad_wait、yield_wait、no_wait的行为差异、如何通过CFLAGS和RR宏定制线程行为以及 Round Robin 调度器在源码层面的实现原理。示例背景什么是 Thread-Duelthread_duel 是 RIOT 提供的一个多线程“对决”演示程序多个相同优先级的线程并发执行但采用不同的“休息策略”以此直观展示 CPU 时间片在非协作、不可按优先级切分的线程之间的分配情况。该示例的核心价值在于回答一个经典问题当多个线程优先级相同、且都不愿意主动让出 CPU 时调度器如何保证公平从源码看示例共创建 3 个线程T1、T2、T3每个线程循环执行“工作busy waiting—休息—打印进度”的流程见 main.c工作通过ztimer_spin(ZTIMER_USEC, us)忙等模拟真实计算计数每次完成一批工作后将工作量累加休息根据配置的策略休息打印每完成一批工作PRINT_STEPS步打印一次进度避免刷屏。线程默认配置在 main.c 中给出三个线程默认均为{no_wait, 5}。四种休息策略线程公平性的核心变量示例定义了四种休息策略定义于 main.c它们对调度的影响截然不同策略底层实现行为特征对调度的印象nice_waitztimer_sleep(ZTIMER_USEC, us)主动让出 CPU进入睡眠等待定时器唤醒友好给其他线程充分运行时间bad_waitztimer_spin(ZTIMER_USEC, us)忙等持续查看时钟期间独占 CPU不友好休息时仍霸占 CPUyield_waitthread_yield()不等待直接让位给同优先级/更高优先级线程无休止地工作但不断主动让位no_wait空操作从不休息、从不让位极端占用直到被抢占其中ztimer_sleep()会挂起当前线程而ztimer_spin()只是忙等待ztimer.h 中的ztimer_sleep与ztimer_spin声明两者的本质区别正是“主动让出 CPU”与“占着 CPU 等时间”的区别。每个线程的worker_config结构main.c包含两个字段waitfn是休息策略函数指针workload是每批工作量0~10。在thread_worker中工作量超出STEPS_PER_SET10时会回退为一半保证工作/休息比例始终有效。编译与运行默认启用 Round Robin构建该示例非常简单MakefileMakefile关键配置如下BOARD ? native RIOTBASE ? $(CURDIR)/../../.. RR ? 1 USEMODULE ztimer_usec ifeq (1,$(RR)) USEMODULE sched_round_robin endif DEVELHELP ? 1要点默认BOARDnative可在 PC 上直接运行观察调度行为RR1默认时自动引入sched_round_robin模块即默认开启轮转调度关闭轮转调度需编译时传入RR0示例依赖ztimer_usec模块Round Robin 定时器基于 ztimer 实现。运行默认示例cd examples/advanced/thread_duel make flash term自定义线程行为CFLAGS 实战通过CFLAGS可以在不修改源码的情况下定制每个线程的策略和工作量CFLAGS-DTHREAD_1{yield_wait,3} -DTHREAD_2{bad_wait,2} RR0 make该命令的效果与原文档一致线程 1采用yield_wait策略每批完成 3 个单位工作后让位线程 2采用bad_wait策略每批完成 2 个单位工作休息时忙等霸占 CPU关闭 Round RobinRR0。对应的宏定义在 main.c 中通过#ifndef THREAD_1/2/3提供默认值因此命令行传入的宏会覆盖默认值。三个线程的配置宏必须符合worker_config结构体的初始化语法{rest_strategy,work}。预期结果分析原文档给出了关闭 Round Robin 后上述配置的预期行为CPU 被线程 2 独占而它只把 20% 的时间用于真正工作2/10的工作占比效率极低线程 1虽然完成了 3 个单位工作但永远没有机会打印进度线程 3从未获得任何执行机会。这正是“非协作 同优先级 无抢占调度”下的典型不公平场景一旦某个线程如bad_wait或no_wait拿到 CPU 就再也不会放手。Round Robin 调度器源码剖析要理解为什么RR1能解决上述问题需要看 sched_round_robin.c 的实现初始化sched_round_robin_init()由AUTO_INIT机制在系统启动时自动调用见 auto_init.c将当前轮转优先级置为0xff未初始化并检查当前活动线程的优先级是否适用轮转。定时器驱动调度器使用静态ztimer_t _rr_timer超时时间为SCHED_RR_TIMEOUT。该宏默认在 sched_round_robin.h 中定义若启用了ztimer_msec则为10单位即毫秒即 10ms否则为10000配合ZTIMER_USEC。时间基准SCHED_RR_TIMERBASE默认优先使用ZTIMER_MSEC否则退化为ZTIMER_USEC。时间片轮转定时器回调_sched_round_robin_cb()将当前轮转优先级对应的就绪队列“轮转”一次sched_runq_advance(prio)把当前线程放到该优先级队列末尾然后触发重新调度。这样即使线程从不主动让位也会被定期剥夺 CPU。优先级掩码SCHED_RR_MASK默认(1 0)即最高优先级 0 不参与轮转——该优先级的线程在设计上假定不会被抢占sched_round_robin.h。所有配置项超时、时间基准、掩码均可通过编译宏覆盖。触发条件sched_runq_callback()仅在同一优先级就绪队列中多于一个线程时才会启动定时器sched_runq_more_than_one(prio)单线程的优先级不会空转定时器。该模块的设计目标见头文件 模块文档是在低资源占用一个静态定时器加一个uint8_t状态变量与公平性之间取得平衡特别适用于“线程无法划分优先级、也无法保证协作”的场景——与 thread_duel 的示例场景完全对应。关于时间片的另一个细节thread_yield()只将当前线程放到就绪队列末尾参见 thread.h 中thread_yield与thread_yield_higher的注释因此yield_wait线程在没有 Round Robin 时也能让同优先级线程轮流执行而bad_wait/no_wait线程从不调用它才会出现原文档描述的 CPU 独占现象。进一步验证配套测试仓库在tests/sys/sched_round_robin提供了针对该模块的自动化测试README.md# 默认包含 sched_round_robin 模块 make tests/sys_sched_round_robin flash term # 关闭轮转调度对比测试 NORR1 make tests/sys_sched_round_robin flash term该测试main.c与 thread_duel 示例相互印证前者以断言形式验证调度器行为后者以可视化方式直观展示同一机制。阅读两者结合可以完整掌握 RIOT 同优先级线程公平调度的设计思路与验证方法。小结thread_duel用 3 个同优先级线程和 4 种休息策略生动演示了协作式调度失效时 CPU 被独占的问题sched_round_robin模块通过 ztimer 驱动的周期性抢占为同优先级线程提供公平的时间片轮转默认 10ms 间隔、可配置时间基准与优先级掩码编译时通过RR0/1开关轮转调度通过CFLAGS-DTHREAD_x{策略,工作量}定制线程无需改动源码即可进行丰富的调度实验结合 main.c、sched_round_robin.c 与 sched_round_robin.h 阅读可以深入理解 RIOT 调度器从机制到实现的完整链路。【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻