RIOT 多线程调度实战:用 thread_duel 示例深入理解 Round Robin 调度与线程公平性
2026/9/19 23:29:36 网站建设 项目流程

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_waitbad_waityield_waitno_wait)的行为差异、如何通过CFLAGSRR宏定制线程行为,以及 Round Robin 调度器在源码层面的实现原理。

示例背景:什么是 Thread-Duel

thread_duel 是 RIOT 提供的一个多线程“对决”演示程序:多个相同优先级的线程并发执行,但采用不同的“休息策略”,以此直观展示 CPU 时间片在非协作、不可按优先级切分的线程之间的分配情况。

该示例的核心价值在于回答一个经典问题:当多个线程优先级相同、且都不愿意主动让出 CPU 时,调度器如何保证公平?从源码看,示例共创建 3 个线程(T1、T2、T3),每个线程循环执行“工作(busy waiting)—休息—打印进度”的流程(见 main.c):

  1. 工作:通过ztimer_spin(ZTIMER_USEC, us)忙等模拟真实计算;
  2. 计数:每次完成一批工作后将工作量累加;
  3. 休息:根据配置的策略休息;
  4. 打印:每完成一批工作(PRINT_STEPS步)打印一次进度,避免刷屏。

线程默认配置在 main.c 中给出,三个线程默认均为{no_wait, 5}

四种休息策略:线程公平性的核心变量

示例定义了四种休息策略(定义于 main.c),它们对调度的影响截然不同:

策略底层实现行为特征对调度的印象
nice_waitztimer_sleep(ZTIMER_USEC, us)主动让出 CPU,进入睡眠等待定时器唤醒友好,给其他线程充分运行时间
bad_waitztimer_spin(ZTIMER_USEC, us)忙等(持续查看时钟),期间独占 CPU不友好,休息时仍霸占 CPU
yield_waitthread_yield()不等待,直接让位给同优先级/更高优先级线程无休止地工作,但不断主动让位
no_wait空操作从不休息、从不让位极端占用,直到被抢占

其中ztimer_sleep()会挂起当前线程,而ztimer_spin()只是忙等待(ztimer.h 中的ztimer_sleepztimer_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 RobinRR=0)。

对应的宏定义在 main.c 中通过#ifndef THREAD_1/2/3提供默认值,因此命令行传入的宏会覆盖默认值。三个线程的配置宏必须符合worker_config结构体的初始化语法{<rest_strategy>,<work>}

预期结果分析

原文档给出了关闭 Round Robin 后上述配置的预期行为:

  • CPU 被线程 2 独占,而它只把 20% 的时间用于真正工作(2/10的工作占比),效率极低;
  • 线程 1虽然完成了 3 个单位工作,但永远没有机会打印进度;
  • 线程 3从未获得任何执行机会。

这正是“非协作 + 同优先级 + 无抢占调度”下的典型不公平场景:一旦某个线程(如bad_waitno_wait)拿到 CPU 就再也不会放手。

Round Robin 调度器源码剖析

要理解为什么RR=1能解决上述问题,需要看 sched_round_robin.c 的实现:

  1. 初始化sched_round_robin_init()AUTO_INIT机制在系统启动时自动调用(见 auto_init.c),将当前轮转优先级置为0xff(未初始化),并检查当前活动线程的优先级是否适用轮转。

  2. 定时器驱动:调度器使用静态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

  3. 时间片轮转:定时器回调_sched_round_robin_cb()将当前轮转优先级对应的就绪队列“轮转”一次(sched_runq_advance(prio)),把当前线程放到该优先级队列末尾,然后触发重新调度。这样即使线程从不主动让位,也会被定期剥夺 CPU。

  4. 优先级掩码SCHED_RR_MASK默认(1 << 0),即最高优先级 0 不参与轮转——该优先级的线程在设计上假定不会被抢占(sched_round_robin.h)。所有配置项(超时、时间基准、掩码)均可通过编译宏覆盖。

  5. 触发条件sched_runq_callback()仅在同一优先级就绪队列中多于一个线程时才会启动定时器(sched_runq_more_than_one(prio)),单线程的优先级不会空转定时器。

该模块的设计目标(见头文件 模块文档)是在低资源占用(一个静态定时器加一个uint8_t状态变量)与公平性之间取得平衡,特别适用于“线程无法划分优先级、也无法保证协作”的场景——与 thread_duel 的示例场景完全对应。

关于时间片的另一个细节:thread_yield()只将当前线程放到就绪队列末尾(参见 thread.h 中thread_yieldthread_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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询