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 |
| 线程状态 | 就绪、运行、阻塞、挂起、关闭 | 由内核自动维护 | 阻塞态不参与调度,需被唤醒 |
三、调度器行为:抢占、轮转与临界区
RT-Thread 调度器在以下时机触发:线程主动让出(延时、阻塞)、中断退出、线程被唤醒且优先级高于当前线程、时间片耗尽。调度器从就绪队列中选择最高优先级线程运行。若多个线程优先级相同,则按时间片轮转。
在临界区或中断中,调度器可能被挂起。使用 rt_enter_critical() 和 rt_exit_critical() 可以暂时禁止调度,保护共享资源。但临界区应尽量短,否则会影响系统实时性。
四、线程同步与通信机制回顾
多线程环境下,共享资源的访问必须同步,线程之间也需要通信。RT-Thread 提供了多种机制,按用途可分为同步和通信两类。
- 信号量:用于线程同步和资源计数,支持释放和获取操作,常用于生产者-消费者模型。
- 互斥量:具有优先级继承机制,用于保护临界区,避免优先级反转。
- 事件集:支持“或”与“与”触发,适合多事件等待场景。
- 邮箱:传递固定长度消息,开销小,适合少量数据通信。
- 消息队列:支持变长消息,适合线程间批量数据传输。
五、典型场景与误区复盘
假设一个数据采集系统:采集线程优先级 10,处理线程优先级 15,显示线程优先级 20。采集线程通过消息队列发送数据,处理线程接收并计算,显示线程刷新界面。若采集线程长时间占用 CPU,处理线程无法及时响应,应通过时间片或主动延时让出 CPU。
常见误区包括:
- 线程栈分配过小,导致栈溢出,系统崩溃或行为异常。
- 在中断服务程序中调用可能引起阻塞的 API,如信号量获取。
- 多个线程共享资源却未使用互斥量保护,导致数据竞争。
- 优先级设置不合理,高优先级线程频繁唤醒,低优先级线程饥饿。
- 忘记调用
rt_thread_startup(),线程创建后未进入就绪态。
list_thread 命令查看线程状态、优先级、栈使用率和剩余时间片,快速定位阻塞或栈溢出问题。