4. 线程与调度基础

在 RT-Thread 中,线程是最基本的调度单位,也是应用逻辑的主要载体。理解线程的创建方式、优先级规则、时间片轮转、调度器行为以及线程间同步与通信机制,是后续使用组件与中间件的前提。本节将把这些基础概念串成一条完整的知识链,帮助你在实际开发中快速定位线程行为异常、优先级反转、通信阻塞等问题。

一、线程创建:静态与动态两条路径

RT-Thread 提供两种线程创建方式:静态创建和动态创建。静态创建使用 rt_thread_init(),线程控制块和栈由用户预先分配,适合资源受限或对确定性要求高的场景;动态创建使用 rt_thread_create(),由系统从堆中分配线程控制块和栈,使用灵活但需关注堆空间。

/* 动态创建线程示例 */
rt_thread_t tid;
tid = rt_thread_create("led_thread",
                       led_thread_entry,
                       RT_NULL,
                       512,
                       20,
                       10);
if (tid != RT_NULL)
    rt_thread_startup(tid);

创建完成后必须调用 rt_thread_startup() 将线程加入就绪队列,否则线程不会参与调度。线程入口函数通常是一个无限循环,内部通过延时、信号量或消息队列等机制主动让出 CPU。

二、优先级与时间片:调度器的两个核心参数

RT-Thread 默认支持 256 个优先级,数值越小优先级越高,0 为最高优先级,通常保留给系统线程。调度器采用基于优先级的抢占式调度:当更高优先级线程就绪时,当前线程立即被抢占。相同优先级的线程之间采用时间片轮转调度,每个线程运行一个时间片后让出 CPU。

参数 含义 典型取值 注意事项
优先级 决定线程被调度的先后顺序 0~255,数值越小越高 避免多个线程使用同一高优先级导致饥饿
时间片 同优先级线程轮转的时间长度 5~20 个 tick 时间片过小增加切换开销,过大影响响应
栈大小 线程独立栈空间 256~2048 字节 过小会栈溢出,过大浪费 RAM
线程状态 就绪、运行、阻塞、挂起、关闭 由内核自动维护 阻塞态不参与调度,需被唤醒
优先级与时间片共同决定线程的实时性。高优先级线程应尽量短小,避免长时间占用 CPU;低优先级线程可通过时间片轮转获得执行机会。

三、调度器行为:抢占、轮转与临界区

RT-Thread 调度器在以下时机触发:线程主动让出(延时、阻塞)、中断退出、线程被唤醒且优先级高于当前线程、时间片耗尽。调度器从就绪队列中选择最高优先级线程运行。若多个线程优先级相同,则按时间片轮转。

在临界区或中断中,调度器可能被挂起。使用 rt_enter_critical() 和 rt_exit_critical() 可以暂时禁止调度,保护共享资源。但临界区应尽量短,否则会影响系统实时性。

RT-Thread 线程调度与同步通信全景 调度器 优先级抢占 + 时间片轮转 就绪队列 高优先级线程 同优先级轮转 运行线程 占用 CPU 时间片递减 阻塞/挂起 等待信号量/消息 延时或挂起 事件唤醒 / 超时返回就绪态 同步机制 信号量 / 互斥量 / 事件集 通信机制 邮箱 / 消息队列

四、线程同步与通信机制回顾

多线程环境下,共享资源的访问必须同步,线程之间也需要通信。RT-Thread 提供了多种机制,按用途可分为同步和通信两类。

  • 信号量:用于线程同步和资源计数,支持释放和获取操作,常用于生产者-消费者模型。
  • 互斥量:具有优先级继承机制,用于保护临界区,避免优先级反转。
  • 事件集:支持“或”与“与”触发,适合多事件等待场景。
  • 邮箱:传递固定长度消息,开销小,适合少量数据通信。
  • 消息队列:支持变长消息,适合线程间批量数据传输。
选择同步与通信机制时,优先考虑数据量、实时性和是否涉及优先级继承。互斥量适合保护共享资源,信号量适合事件通知,消息队列适合数据传递。

五、典型场景与误区复盘

假设一个数据采集系统:采集线程优先级 10,处理线程优先级 15,显示线程优先级 20。采集线程通过消息队列发送数据,处理线程接收并计算,显示线程刷新界面。若采集线程长时间占用 CPU,处理线程无法及时响应,应通过时间片或主动延时让出 CPU。

常见误区包括:

  1. 线程栈分配过小,导致栈溢出,系统崩溃或行为异常。
  2. 在中断服务程序中调用可能引起阻塞的 API,如信号量获取。
  3. 多个线程共享资源却未使用互斥量保护,导致数据竞争。
  4. 优先级设置不合理,高优先级线程频繁唤醒,低优先级线程饥饿。
  5. 忘记调用 rt_thread_startup(),线程创建后未进入就绪态。
调试线程问题时,可使用 list_thread 命令查看线程状态、优先级、栈使用率和剩余时间片,快速定位阻塞或栈溢出问题。