RT-Thread快速入门(二)之线程优先级和Tick 前言在上一篇《RT-Thread快速入门一之线程创建与删除》中我们学习了线程的基本概念以及如何创建和删除线程。但线程创建之后哪个线程先运行每个线程运行多久这就涉及到线程的优先级和Tick时间片机制。如果把系统比作一个厨房优先级就像厨房着火了当然先灭火——紧急的事情先做。Tick时间片就像喂饭和回信息同样重要轮流做——每件事做一会再换下一件。本文将深入讲解 RT-Thread 中线程的优先级、Tick 的概念及调度机制并通过完整的代码示例帮助大家理解。目录前言一、线程优先级Priority1.1 什么是优先级1.2 优先级范围1.3 调度规则二、Tick系统滴答和时间片2.1 什么是 Tick2.2 什么是时间片Timeslice2.3 优先级与时间片的配合三、线程状态详解3.1 初始状态RT_THREAD_INIT3.2 就绪状态RT_THREAD_READY3.3 运行状态RT_THREAD_RUNNING3.4 挂起状态RT_THREAD_SUSPEND① 时间相关的事件② 同步事件3.5 关闭状态RT_THREAD_CLOSE3.6 状态一览表四、代码示例示例1优先级实验 — 高优先级抢占 同优先级轮转示例2修改优先级五、调度机制总结六、常见问题FAQQ1如何查看当前系统中 Tick 的频率Q2空闲线程的优先级是多少Q3如何查看当前运行线程的优先级Q4优先级设置太高会有什么问题Q5时间片设多大合适七、总结一、线程优先级Priority1.1 什么是优先级线程优先级是指线程被调度的优先程度。每个线程都具有一个优先级线程的重要性越高优先级应该设置得越高被调度的可能性才会更大。1.2 优先级范围RT-Thread 的优先级由rtconfig.h中的RT_THREAD_PRIORITY_MAX宏定义// rtconfig.h 中的配置 #define RT_THREAD_PRIORITY_MAX 32 // 支持 32 个优先级最常用 // 最大可支持 256 个优先级0~255关键规则规则说明数值越小优先级越高0最高优先级最大数值 - 1如31或255最低优先级默认分配给空闲线程用户线程不应使用最低优先级常见配置ARM Cortex-M 系列通常使用32 个优先级资源充足时可配置为 256 级。1.3 调度规则在学习调度方法之前只需要记住两条核心规则规则一RT-Thread 会确保最高优先级的、可运行的线程马上就能执行。规则二对于相同优先级的、可运行的线程轮流执行时间片轮转。用人话来说就是厨房着火了→ 不管在做什么先灭火高优先级抢占。喂饭和回信息同样重要→ 喂一口饭回一句信息轮流来同优先级轮转。二、Tick系统滴答和时间片2.1 什么是 Tick人有心跳心跳间隔基本恒定。RT-Thread 中也有心跳——它使用定时器产生固定间隔的中断这叫Tick滴答、时钟节拍。// rtconfig.h — 默认配置 #define RT_TICK_PER_SECOND 1000 // 每秒 1000 个 Tick这意味着每个 Tick 间隔为1ms1000ms ÷ 1000 Tick。Tick 是整个 RTOS 的心跳它的作用包括提供时间基准延时、超时计算时间片决定线程能运行多久触发调度器重新评估哪个线程应该运行2.2 什么是时间片Timeslice对于同优先级的线程它们轮流执行。怎么轮流你执行一会我执行一会。这个一会就是时间片。时间片 线程一次调度中可以连续运行的最大 Tick 数时间轴: t1 t2 t3 t4 t5 t6 t7 | | | | | | | Tick 计数: 0 1 2 3 4 5 6 ↓ ← 时间片(Tick Period) →时间片的作用示意假设有两个优先级相同的就绪态线程 A 和 BA 的时间片 10个 TickB 的时间片 5个 Tick时间轴: t1 t15 t115 t120 t130 ... 线程A: ░░░░░ ████████████ ░░░░░ ████████████ ... 线程B: █████ ░░░░░░░░░░░░ █████ ░░░░░░░░░░░░ ... ↑B运行 ↑A运行 ↑B运行 ↑A运行 5 Tick 10 Tick 5 Tick 10 TickB 运行 5 个 Tick → A 运行 10 个 Tick → B 运行 5 个 Tick → …… 不断轮转。2.3 优先级与时间片的配合场景调度策略例子不同优先级抢占式高优先级就绪 → 立即抢占低优先级优先级 14 的线程抢了优先级 15 的线程相同优先级时间片轮转轮流执行用完时间片就切换两个优先级 15 的线程轮流运行无就绪高优先级 仅一个同优先级一直运行就绪队列里只有它三、线程状态详解理解优先级和 Tick 后再来看线程状态就更容易了。线程有5 种状态3.1 初始状态RT_THREAD_INIT当线程刚开始创建、还没有开始运行时就处于初始状态使用rt_thread_init()创建但未调用rt_thread_startup()使其就绪。使用rt_thread_create()创建但未调用rt_thread_startup()使其就绪。初始状态下的线程只是一个空壳还没有进入调度队列不会参与 CPU 竞争。3.2 就绪状态RT_THREAD_READY这个线程完全准备好了随时可以运行只是暂时还没轮到它。以下情况线程会处于就绪状态场景说明刚启动调用rt_thread_startup()后进入就绪态被抢占运行时被更高优先级线程抢占了 CPU时间片用完轮到同优先级线程运行了被唤醒等待的资源就绪或被中断唤醒后进入就绪态就绪状态 万事俱备只欠 CPU3.3 运行状态RT_THREAD_RUNNING当调度器从就绪队列中选中某个线程该线程就进入运行状态真正开始使用 CPU 执行代码。一个 CPU 核在任意时刻只有一个线程处于运行状态。3.4 挂起状态RT_THREAD_SUSPEND生活类比母亲在电脑前跟同事沟通时如果同事一直没回复母亲的工作就被卡住了、被堵住了——这就是挂起状态。重点在于线程在等待。在实际产品中我们不会让一个线程一直运行而是使用事件驱动的方法让它运行线程要等待某个事件事件发生后它才能运行。在等待事件过程中它不消耗 CPU 资源。在等待事件的过程中这个线程就处于挂起状态。挂起状态的线程可以等待两种类型的事件① 时间相关的事件等待方式说明例子相对延时等待一段时间rt_thread_mdelay(10)等 10ms绝对时间等到某个特定时刻等到下午 3:00② 同步事件这类事件由其他线程或中断服务程序产生同步源说明典型函数信号量Semaphore等待某个资源可用rt_sem_take()互斥量Mutex等待获得互斥锁rt_mutex_take()事件集Event等待一个或多个事件发生rt_event_recv()消息队列Message Queue等待接收消息rt_mq_recv()邮箱Mailbox等待邮件rt_mb_recv()超时机制在等待一个同步事件时可以加上超时时间。以等待队列数据为例/* 等待队列数据超时 10ms */ rt_mq_recv(mq, msg, sizeof(msg), 10); // ✓ 10ms 之内有数据到来成功返回 // ✗ 10ms 到了还是没有数据超时返回 (-RT_ETIMEOUT)3.5 关闭状态RT_THREAD_CLOSE当线程运行结束时将处于关闭状态可由运行状态正常退出顺序执行模式的函数执行完毕进入关闭状态或者通过线程删除函数进入关闭状态删除函数适用对象rt_thread_detach(thread)使用rt_thread_init()创建的静态线程rt_thread_delete(thread)使用rt_thread_create()创建的动态线程进入关闭状态后线程的资源控制块、栈会在空闲线程中回收。3.6 状态一览表状态枚举值说明初始状态RT_THREAD_INIT线程刚创建未调用startup未进入调度队列就绪状态RT_THREAD_READY随时可以运行只等调度器分配 CPU运行状态RT_THREAD_RUNNING正在使用 CPU 执行代码挂起状态RT_THREAD_SUSPEND等待事件延时/同步资源不消耗 CPU关闭状态RT_THREAD_CLOSE线程结束等待空闲线程回收资源四、代码示例示例1优先级实验 — 高优先级抢占 同优先级轮转本程序创建 3 个线程thread1、thread2优先级相同都是 15。thread3优先级最高是 14THREAD_PRIORITY-1。#include rtthread.h ​ /* 线程入口函数3个线程都使用同一函数参数区分 */ static void thread_entry(void *parameter) { const char *thread_name parameter; ​ while(1) { rt_kprintf(thread_name); rt_thread_delay(100); // 延时 100 Tick } } ​ /* 线程参数不同的字符串 */ static const char *thread1_name Thread1 run\r\n; static const char *thread2_name Thread2 run\r\n; static const char *thread3_name Thread3 run\r\n; ​ /* 线程句柄 */ static rt_thread_t thread1 RT_NULL; static rt_thread_t thread2 RT_NULL; static rt_thread_t thread3 RT_NULL; ​ #define THREAD_PRIORITY 15 #define THREAD_STACK_SIZE 512 #define THREAD_TIMESLICE 10 ​ int main(void) { /* 创建 thread1 — 优先级 15 */ thread1 rt_thread_create(thread1, thread_entry, (void *)thread1_name, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); if (thread1 ! RT_NULL) rt_thread_startup(thread1); ​ /* 创建 thread2 — 优先级 15与 thread1 相同 */ thread2 rt_thread_create(thread2, thread_entry, (void *)thread2_name, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); if (thread2 ! RT_NULL) rt_thread_startup(thread2); ​ /* 创建 thread3 — 优先级 14最高 */ thread3 rt_thread_create(thread3, thread_entry, (void *)thread3_name, THREAD_STACK_SIZE, THREAD_PRIORITY - 1, // 优先级更高 THREAD_TIMESLICE); if (thread3 ! RT_NULL) rt_thread_startup(thread3); ​ return 0; }实验现象调度执行顺序1. thread3优先级14优先执行 → 调用 rt_thread_delay(100) 后挂起。2. thread1 和 thread2优先级15轮流执行时间片轮转。3. thread3 延时结束后 → 再次抢占 CPU 4. 如此循环...示例2修改优先级RT-Thread 提供rt_thread_control()函数可以在运行时动态修改线程优先级rt_err_t rt_thread_control(rt_thread_t thread, int cmd, void *arg);控制命令cmd常用取值命令作用RT_THREAD_CTRL_CHANGE_PRIORITY修改优先级RT_THREAD_CTRL_STARTUP启动线程RT_THREAD_CTRL_CLOSE关闭线程完整示例#include rtthread.h static rt_thread_t thread1 RT_NULL; static rt_thread_t thread2 RT_NULL; static const char *thread1_name Thread1; static const char *thread2_name Thread2; #define THREAD_PRIORITY 15 #define THREAD_STACK_SIZE 512 #define THREAD_TIMESLICE 10 /* 线程1运行时不断提升线程2的优先级 */ static void thread1_entry(void *parameter) { rt_uint8_t priority; while(1) { rt_kprintf(%s: thread1 priority%d, thread2 priority%d\n\r, parameter, thread1-current_priority, thread2-current_priority); /* 如果线程1优先级 ≥ 线程2优先级就提升线程2 */ if (thread1-current_priority thread2-current_priority) { priority thread2-current_priority - 1; rt_thread_control(thread2, RT_THREAD_CTRL_CHANGE_PRIORITY, priority); rt_schedule(); // 主动触发调度 } } } /* 线程2只打印信息 */ static void thread2_entry(void *parameter) { while(1) { rt_kprintf(%s: thread1 priority%d, thread2 priority%d\n\r, parameter, thread1-current_priority, thread2-current_priority); } } int main(void) { /* 创建 thread1 — 优先级 14更高 */ thread1 rt_thread_create(thread1, thread1_entry, (void *)thread1_name, THREAD_STACK_SIZE, THREAD_PRIORITY - 1, THREAD_TIMESLICE); if (thread1 ! RT_NULL) rt_thread_startup(thread1); /* 创建 thread2 — 优先级 15 */ thread2 rt_thread_create(thread2, thread2_entry, (void *)thread2_name, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); if (thread2 ! RT_NULL) rt_thread_startup(thread2); return 0; }实验现象阶段1thread1pri14优先级更高先执行 → 发现自己的优先级比 thread2 高 → 提升 thread2 的优先级pri15 → 14。阶段2thread1 调用 rt_schedule() 触发调度 → 此时 thread2 的优先级也是 14 → thread2 得到了执行机会。阶段3thread2 执行后thread1 再次检查继续提升 thread2 的优先级 → thread2一直执行。⚠️注意修改优先级后调用rt_schedule()可立即触发一次调度让新的优先级设置立即生效。五、调度机制总结RT-Thread 的线程调度是抢占式 时间片轮转的混合调度算法特性说明调度算法完全抢占式时间片轮转优先级范围8 / 32 / 256 级由RT_THREAD_PRIORITY_MAX配置优先级数值越小优先级越高0 为最高最低优先级默认预留给空闲线程Idle Thread时间片单位1 Tick默认 1ms时间片配置创建线程时通过tick参数指定六、常见问题FAQQ1如何查看当前系统中 Tick 的频率A在rtconfig.h中查看RT_TICK_PER_SECOND宏定义默认 1000。Q2空闲线程的优先级是多少A空闲线程使用最低优先级如 32 级中的第 31 级256 级中的第 255 级用户不应使用该优先级创建线程。Q3如何查看当前运行线程的优先级A可通过thread-current_priority获取或在串口终端输入list_thread命令查看。Q4优先级设置太高会有什么问题A如果某个线程优先级过高且不主动延时/挂起会导致低优先级线程永远无法运行线程饿死Starvation。因此优先级设计要根据任务的实际紧急程度合理分配。Q5时间片设多大合适A取决于任务性质计算密集型任务时间片可设大一些减少切换开销I/O 密集型任务时间片可设小一些提高响应速度通常建议设为5~20 个 Tick七、总结知识点要点优先级数值越小优先级越高0 为最高最低优先级预留给空闲线程抢占式调度高优先级就绪时立刻抢占低优先级时间片轮转同优先级线程轮流执行时间片用完就切换Tick系统心跳默认 1ms是时间的基本单位修改优先级rt_thread_control(thread, RT_THREAD_CTRL_CHANGE_PRIORITY, pri)线程状态INIT → READY → RUNNING → SUSPEND → CLOSE含超时机制、同步事件等