4. 线程管理与调度实战
在 RT-Thread 中,线程是最小的调度单位,也是量产项目里最常被直接操作的对象。理解线程的完整生命周期,比记住几个 API 更重要:一个线程从创建到删除,中间会经历就绪、运行、挂起、关闭等状态,而调度器则根据优先级和时间片在这些状态之间做决策。本章以“多线程 LED 与串口”案例为主线,把线程创建、删除、挂起、恢复、优先级和时间片轮转串起来,并落到线程通信的实战上。
一、核心原理:线程状态与调度器
RT-Thread 的线程有五个基本状态:初始态(RT_THREAD_INIT)、就绪态(RT_THREAD_READY)、运行态(RT_THREAD_RUNNING)、挂起态(RT_THREAD_SUSPEND)和关闭态(RT_THREAD_CLOSE)。创建线程后它进入初始态,调用 rt_thread_startup() 后进入就绪态;调度器从就绪队列中选出优先级最高的线程运行。当高优先级线程就绪时,会立即抢占当前低优先级线程,这就是 RT-Thread 的抢占式调度。
优先级数值越小,优先级越高,RT-Thread 默认支持 0~31 共 32 个优先级,其中 0 最高。相同优先级的线程之间采用时间片轮转:每个线程被分配一个时间片(tick),运行完自己的时间片后让出 CPU,排到同优先级就绪队列的末尾。因此“优先级决定谁先跑,时间片决定同优先级谁跑多久”。
二、方法流程:线程操作的标准动作
线程操作有一套固定流程,建议按“创建 → 启动 → 运行 → 挂起/恢复 → 删除”的顺序理解。创建分为动态和静态两种:动态使用 rt_thread_create(),从堆中分配线程控制块和栈;静态使用 rt_thread_init(),由用户提供线程控制块和栈空间,适合对内存确定性要求高的量产项目。
/* 动态创建线程 */
rt_thread_t tid = rt_thread_create("led",
led_thread_entry,
RT_NULL,
512, /* 栈大小 */
20, /* 优先级 */
10); /* 时间片 */
if (tid != RT_NULL)
rt_thread_startup(tid);
/* 挂起与恢复 */
rt_thread_suspend(tid);
rt_thread_resume(tid);
/* 删除线程 */
rt_thread_delete(tid);
挂起和恢复常用于“临时停止某个任务”的场景,例如串口升级时暂停 LED 闪烁。需要注意:挂起一个正在运行的线程后,必须由其他线程调用 rt_thread_resume() 才能恢复;如果线程自己挂起自己,则必须等待外部事件唤醒。删除线程时,动态创建的线程由系统回收栈和线程控制块,静态线程则需要先 rt_thread_detach() 再自行释放资源。
三、案例场景:多线程 LED 与串口通信
下面用一个典型量产场景说明:系统中有三个线程——LED 闪烁线程(优先级 20)、串口接收线程(优先级 15)、串口命令解析线程(优先级 18)。串口接收线程通过信号量通知解析线程,解析线程根据命令控制 LED 线程的挂起与恢复。这样既演示了优先级抢占,也演示了线程间通信。
/* 串口接收线程:收到数据释放信号量 */
void uart_rx_thread_entry(void *param)
{
while (1) {
rt_size_t len = rt_device_read(uart_dev, 0, buf, sizeof(buf));
if (len > 0) {
rt_mb_send(&cmd_mb, (rt_ubase_t)buf);
}
rt_thread_mdelay(10);
}
}
/* 命令解析线程:根据命令挂起/恢复 LED 线程 */
void cmd_thread_entry(void *param)
{
rt_ubase_t msg;
while (1) {
if (rt_mb_recv(&cmd_mb, &msg, RT_WAITING_FOREVER) == RT_EOK) {
if (msg == CMD_LED_OFF)
rt_thread_suspend(led_tid);
else if (msg == CMD_LED_ON)
rt_thread_resume(led_tid);
}
}
}
运行时可观察到:串口接收线程优先级最高,收到数据后立即抢占;解析线程优先级居中,处理命令;LED 线程优先级最低,在空闲时闪烁。当发送“LED 关闭”命令后,LED 线程被挂起,不再参与调度;发送“LED 开启”后恢复。整个过程体现了优先级、挂起恢复和线程通信的协同。
四、参数与行为对照
| 操作 | API | 状态变化 | 典型用途 |
|---|---|---|---|
| 动态创建 | rt_thread_create | 初始态 → 就绪态 | 常规任务,灵活分配 |
| 静态创建 | rt_thread_init | 初始态 → 就绪态 | 内存确定性要求高 |
| 启动 | rt_thread_startup | 初始态 → 就绪态 | 让线程参与调度 |
| 挂起 | rt_thread_suspend | 就绪/运行 → 挂起态 | 临时停止任务 |
| 恢复 | rt_thread_resume | 挂起态 → 就绪态 | 重新参与调度 |
| 删除 | rt_thread_delete | 任意 → 关闭态 | 释放动态资源 |
| 脱离 | rt_thread_detach | 任意 → 关闭态 | 静态线程释放 |
五、误区与复盘
复盘建议:在量产项目中,优先使用静态创建保证内存可控;对实时性要求高的串口接收线程给较高优先级;对 LED 这类非关键任务给较低优先级并配合时间片;挂起/恢复要成对出现,最好用状态机或命令表管理。掌握这些,线程管理就不再是零散 API,而是一套可复用的工程方法。
把这张状态迁移图记在脑子里,再回头看 rt_thread_create、rt_thread_suspend、rt_thread_resume 这些 API,就能清楚每个调用把线程推到了哪个状态、由谁负责把它拉回来。多线程 LED 与串口案例的价值,正在于把抽象的状态机变成可观察、可调试的真实行为。