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-Duel
thread_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_wait | ztimer_sleep(ZTIMER_USEC, us) | 主动让出 CPU,进入睡眠等待定时器唤醒 | 友好,给其他线程充分运行时间 |
bad_wait | ztimer_spin(ZTIMER_USEC, us) | 忙等(持续查看时钟),期间独占 CPU | 不友好,休息时仍霸占 CPU |
yield_wait | thread_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_SET(10)时会回退为一半,保证工作/休息比例始终有效。
编译与运行:默认启用 Round Robin
构建该示例非常简单,Makefile(Makefile)关键配置如下:
BOARD ?= native RIOTBASE ?= $(CURDIR)/../../.. RR ?= 1 USEMODULE += ztimer_usec ifeq (1,$(RR)) USEMODULE += sched_round_robin endif DEVELHELP ?= 1要点:
- 默认
BOARD=native,可在 PC 上直接运行观察调度行为; RR=1(默认)时自动引入sched_round_robin模块,即默认开启轮转调度;- 关闭轮转调度需编译时传入
RR=0; - 示例依赖
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}' RR=0 make该命令的效果(与原文档一致):
- 线程 1:采用
yield_wait策略,每批完成 3 个单位工作后让位; - 线程 2:采用
bad_wait策略,每批完成 2 个单位工作,休息时忙等霸占 CPU; - 关闭 Round Robin(
RR=0)。
对应的宏定义在 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 调度器源码剖析
要理解为什么RR=1能解决上述问题,需要看 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 # 关闭轮转调度对比测试 NORR=1 make tests/sys_sched_round_robin flash term该测试(main.c)与 thread_duel 示例相互印证:前者以断言形式验证调度器行为,后者以可视化方式直观展示同一机制。阅读两者结合,可以完整掌握 RIOT 同优先级线程公平调度的设计思路与验证方法。
小结
- thread_duel用 3 个同优先级线程和 4 种休息策略,生动演示了协作式调度失效时 CPU 被独占的问题;
sched_round_robin模块通过 ztimer 驱动的周期性抢占,为同优先级线程提供公平的时间片轮转,默认 10ms 间隔、可配置时间基准与优先级掩码;- 编译时通过
RR=0/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),仅供参考