【STM32RT-Thread零基础入门】 4. 线程介绍(理论)
文章目录
- 前言
 - 一、线程的概念
 - 二、线程的调度
 - 三、上下文切换
 - 四、线程的重要属性
 - 1. 线程栈
 - 2. 线程的状态
 - 3. 线程优先级
 - 4. 线程时间片
 - 5. 线程的入口函数
 
- 五、RT-Thread命令查看系统线程信息
 - 总结
 
前言
前文中的最后一个任务发现,一个main()函数很难同时实现按键功能和闪灯功能,就好像人很难同时完成左手画圆右手画方一样,这种情况可以安排一人去画圆、一人去画方,并行进行就很容易了,两人各司其职,互不干扰。
 操作系统中,一个线程就像做事的一个人。一个操作系统可以有多个线程,不同的线程完成不同的小任务,各司其职完成整个系统大任务。
一、线程的概念
日常生活中,完成一个复杂的任务,一般会先将他分解成多个简单、容易解决的小任务,再将小任务分配给不同的人完成,当小任务逐个被完成时,复杂任务也就随之完成了。
嵌入式系统一般也是用于完成一些特定任务而设计的,这些特定任务可能比较复杂,这就要求开发人员把复杂的任务进行功能分解,形成若干个不同功能的小任务,而不同功能的小任务由运行于操作系统中的不同程序来完成,再由操作系统统一协调各个程序之间的运行。这些运行在操作系统之上的程序单元就是线程。
当合理地划分任务并正确地执行时,这种设计能够让系统满足实时系统的性能及时间的要求,例如让嵌入式系统执行这样的任务,系统通过红外传感器采集寻迹数据,并根据数据采集结果决定小车的行驶方向(如直走,纠偏等),在多线程实时系统中,可以将这个任务分解成两个子任务,如下图所示:
 
 子任务1不间断地读取传感器数据,并将数据写到共享内存中。
 子任务2周期性地从共享内存中读取数据,并根据传感器数据修正行进路线。
在 RT-Thread 中,线程是实现任务的载体,它是 RT-Thread 中最基本的调度单位。
二、线程的调度
调度是什么?先讲个故事,小朋友去游乐场玩荡秋千,秋千只有一个,但想玩的小朋友很多,那怎么办?游乐场的服务员小姐姐就想了一个办法:想玩的小朋友要排队,一个一个轮流玩,而且每人一次只能玩5分钟,如果还想再玩一次的话,得重新排队。这样,听话的小朋友们就在服务员小姐姐的统一调度下开心有序的玩起了荡秋千。
对于单核系统,CPU就好比秋千,线程就好比游乐场的小朋友,系统中有多个线程,每个线程的运行都要占有CPU,而CPU只有一个,这样,想让所有线程在操作系统上有序地运行,就需要有一个统一的协调者,这个协调者我们叫它调度器,而它的工作就是负责线程调度,给不同的线程分配运行时间,使操作系统上的所有线程有序地运行。综上所述,线程调度是指按照特定机制为多个线程分配CPU的使用权。
线程调度方式常见的有两种,分别为分时调度和抢占式调度。
 (1)分时调度就是所有线程轮流拥有CPU的使用权,平均分配每个线程占用CPU的时间;
 (2)抢占式调度就是让优先级高的线程优先使用CPU,如果线程的优先级相同,则会像分时调度一样轮流使用CPU。
RT-Thread实时操作系统,采用的调度方式是抢占式调度方式。
三、上下文切换
线程是程序的一个运行实例,线程执行时的运行环境称为上下文,每个线程都有自己的上下文,包括程序的代码、数据、堆栈、寄存器、程序计数器等。
操作系统的调度器在进行线程调度时,会发生上下文件切换,从正在运行的线程的上下文,切换到另一个线程的上下文。相当从一个程序代码,切换到另一个程序代码,但又不仅是代码的切换,上下文件的其它内容,如数据、堆栈、寄存器、程序计数器等也要一起切换。
线程的运行,是需要运行环境的,对于线程来说,运行环境就是CPU资源,包括运算单元、程序指针、堆栈指针以及各种通用寄存器。当线程运行时,它会认为自己是以独占CPU资源的方式在运行,会根据自己需要对这些资源进行修改。
而CPU资源只有一套,当另一个线程运行时,它也想独占CPU资源,那么前面已占用CPU资源的线程必须把CPU资源让给后面的线程使用。但它又担心后面的线程把CPU资源修改了,所以它在让出资源之前,还要做一件重要的事,就是把它当前的资源使用情况记录下来(这个动作也叫保护现场),方便下次运行时重新布置资源。
另一个线程得到资源后,此时得到的资源的状态不适合它运行,因此在运行之前,它必须根据自己的需要把资源重新布置(这个动作也叫恢复现场)才能运行。
这种把CPU资源从一个状态切换到另一个状态的过程,就是上下文切换。
我们拿舞台剧来做个不太恰当的比喻。每一场舞台剧的表演都需要一个舞台资源(场地、灯光和道具等),而且独占舞台资源,表演时会根据自己表演剧情的需要,把舞台资源进行布置。而到下一场表演时,也要独占舞台资源,而且此时的舞台资源布置情况不适合下一场表演的剧情需要,必须在开始前重新布置舞台资源。所在,从一场表演切换到另一场表演时,需要进行舞台场景的切换。
四、线程的重要属性
1. 线程栈
线程栈:用于保存线程的上下文,函数调用过程、局部变量等
进行上下文切换时,需要记录线程的上下文信息,那么就需要有一个地方来保存这些数据。RT-Thread的每一各线程都具有独立的栈,当进行线程切换时,会将当前线程的上下文保存在栈中,当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。
线程栈还用来存放函数中的局部变量,函数中的局部变量从线程空间中申请,在初始时从寄存器中分配(ARM架构),当这个函数再调用另一个函数时,这些局部变量将放入栈中。
线程栈的大小设定:
 (1)对于资源相对较大的MCU,可以适当设计较大的线程栈;
 (2)在初始时设置较大的栈,例如指定大小为1KB或2KB,然后在FinSH中用list_thread命令查看线程运行过程中线程所使用的栈的大小,通过此命令,能够看到从线程启动运行时,到当前时刻点,线程使用的最大栈深度,而后加上适当的余量形成最终的线程栈大小,最后对栈空间加以修改。
2. 线程的状态
系统运行的过程中,同一时间内只允许一个线程在处理器中运行,从运行的过程上划分,线程有多种不同的运行状态。RT-Thread中,操作系统会自动根据其运行的情况来动态调整它的状态。
| 状态 | 描述 | 
|---|---|
| 初始状态 | 当线程刚开始创建还没启动时就处于初始状态;在初始状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_INIT | 
| 就绪状态 | 在就绪状态下,线程按照优先级排队,等待被执行;一旦当前线程运行完毕让出处理器,操作系统会马上寻找最高优先级的就绪态线程运行。此状态在 RT-Thread 中的宏定义为 RT_THREAD_READY | 
| 运行状态 | 线程当前正在运行。在单核系统中,只有 rt_thread_self() 函数返回的线程处于运行状态;在多核系统中,可能就不止这一个线程处于运行状态。此状态在 RT-Thread 中的宏定义为 RT_THREAD_RUNNING | 
| 挂起状态 | 也称阻塞态。它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。在挂起状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_SUSPEND | 
| 关闭状态 | 当线程运行结束时将处于关闭状态。关闭状态的线程也叫僵尸线程,不参与线程的调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_CLOSE | 
RT-Thread提供一系列的操作系统调用接口,使线程的状态在5个状态之间来回切换。几种状态间的切换关系如图所示:
 
- 线程通过调用函数 rt_thread_create/init() 进入到初始状态(RT_THREAD_INIT);
 - 初始状态的线程通过调用函数 rt_thread_startup() 进入到就绪状态(RT_THREAD_READY);
 - 就绪状态的线程被调度器调度后进入运行状态(RT_THREAD_RUNNING);
 - 当处于运行状态的线程调用①中的rt_thread_delay(),rt_sem_take(),rt_mutex_take(),rt_mb_recv()等函数或者获取不到资源时,将进入到挂起状态(RT_THREAD_SUSPEND);
 - 处于挂起状态的线程,如果等待超时依然未能获得资源或由于其他线程释放了资源,那么它将返回到就绪状态;
 - 挂起状态的线程,如果调用rt_thread_delete/detach() 函数,将更改为关闭状态(RT_THREAD_CLOSE);
 - 而运行状态的线程,如果运行结束,就会在线程的最后部分执行 rt_thread_exit() 函数,将状态更改为关闭状态。
 
3. 线程优先级
RT-Thread 线程的优先级是表示线程被调度的优先程度。每个线程都具有优先级,线程越重要,赋予的优先级就应越高,线程被调度的可能才会越大。
RT-Thread 最大支持 256 个线程优先级 (0~255),数值越小的优先级越高,0 为最高优先级。在一些资源比较紧张的系统中,可以根据实际情况选择只支持 8 个或 32 个优先级的系统配置;对于 ARM Cortex-M 系列,普遍采用 32 个优先级。最低优先级默认分配给空闲线程使用,用户一般不使用。在系统中,当有比当前线程优先级更高的线程就绪时,当前线程将立刻被换出,高优先级线程抢占处理器运行。
4. 线程时间片
回到前文小朋友荡秋千,每个小朋友荡秋千的时间是有限的,不能一直占着秋千,否则会导致其他小朋友不能参与秋千。
同样操作系统中的线程也不能一直占用CPU,每个线程都有时间片这个参数,表示线程每次可以占用CPU运行的时间,当时间片用完,线程就要让出CPU。
但时间片仅对优先级相同的就绪态线程有效。系统对于优先级相同的就绪态线程采用时间片轮转的调度方式进行调度,时间片起到约束线程单次运行时长的作用,其单位是一个系统节拍(OS Tick),后面讲时钟时会讲。
假设有2个优先级相同的就绪态线程A与B,A线程的时间片设置为10,B线程的时间片设置为5,那么当系统中不存在比A优先级高的就绪态线程时,系统会在A、B线程间来回切换执行,并且每次对A线程执行10个节拍的时长,对对 B 线程执行 5 个节拍的时长,如下图,可以看出,线程A占用CPU运行时间较长。所以我们要根据程序所执行的所需长度,定时间片长度,以执行完程序。

5. 线程的入口函数
线程的入口函数是线程实现预期功能的函数,它是线程第一个运行的函数,也是线程开始运行的地方。线程控制块中的 entry 是线程的入口函数,它是线程实现预期功能的函数。线程的入口函数由用户设计实现,可以有以下几点模式。
(1)无限循环模式
在实时系统中,线程通常是被动式的,这个是由实时系统的特性所决定的,实时系统通常总是等待外界事件的发生,而后进行相应的服务。
void thread_entry(void* paramenter)
{while (1){/* 等待事件的发生 *//* 对事件进行服务、进行处理 */}
} 
 
线程看似没有什么限制程序执行的因素,似乎所有的操作都可以执行。但是作为一个实时系统,一个优先级明确的实时系统,如果一个线程中的程序陷入了死循环操作,那么比它优先级低的线程都将不能够得到执行。所以在实时操作系统中必须注意的一点就是:线程中不能陷入死循环操作,必须要有让出 CPU 使用权的动作,如循环中调用延时函数或者主动挂起。用户设计这种无限循环的线程的目的,就是为了让这个线程一直被系统循环调度运行,永不删除。
(2)顺序执行或有限次循环模式:
如简单的顺序语句、do while() 或 for()循环等,此类线程不会循环或不会永久循环,可谓是 “一次性” 线程,一定会被执行完毕。在执行完毕后,线程将被系统自动删除。
static void thread_entry(void* parameter)
{/* 处理事务 #1 */…/* 处理事务 #2 */…/* 处理事务 #3 */
}
 
五、RT-Thread命令查看系统线程信息
在第3章 PIN设备(GPIO)的使用中的任务4基础上,使用命令查看系统线程信息。在终端输入命令help,观看输出结果,如图所示:
 
 可以发现help命令可以查看系统支持的所有命令,其中有两个命令与线程相关,分别时ps和list_thread。这两个命令都可以用于查看系统中存在哪些线程,以及线程的信息,其命令输出结果如下图所示:
 
连个命令输出一样,目前系统有4个线程,分别为tshell、tidle0、timer、main。tshell、tidle0、timer三个线程是系统线程,由系统在启动时自动创建并运行。
- tshell是用户线程,在系统开启finsh命令组件时创建此线程(默认是开启的),他负责解析并执行终端命令,最后向终端输出命令执行结果;
 - tidle0是一个空闲线程,在系统空闲的时候运行,用于处理系统中一些非紧急的任务,如僵尸线程的资源清理,就是在空闲线程中进行的;
 - timer是定时器线程,负责处理定时器任务;
 - main就是我们程序中的main函数,单独作为一个线程运行,通常也叫主线程。
 
总结
本章主要讲了线程的基本概念
相关文章:
【STM32RT-Thread零基础入门】 4. 线程介绍(理论)
文章目录 前言一、线程的概念二、线程的调度三、上下文切换四、线程的重要属性1. 线程栈2. 线程的状态3. 线程优先级4. 线程时间片5. 线程的入口函数 五、RT-Thread命令查看系统线程信息总结 前言 前文中的最后一个任务发现,一个main()函数很难同时实现按键功能和闪…...
css学习3(三种样式表与样式控制优先级)
1、外部样式表:当样式需要应用于很多页面时,外部样式表将是理想的选择。在使用外部样式表的情况下,你可以通过改变一个文件来改变整个站点的外观。每个页面使用 <link> 标签链接到样式表,也要放到<head>中。 2、外部…...
【JS深层解析】内存
前言 接触过 C 语言的小伙伴应该了解:C语言拥有底层最原始的内存管理办法,例如:malloc() 和 free()方法,它们被开发者用来从操作系统中分配和释放内存。 然而在JavaScript这门语言中,当我们创建了一个东西(可以被读写、传递的数据),浏览器中的 js 引擎会根据创建这个…...
第二章:25+ Python 数据操作教程(第十四节NUMPY 教程与练习)
NumPy(“Numerical Python”或“Numeric Python”的缩写)是 Python 中对数组和矩阵进行快速数学计算的最基本的软件包之一。在处理多维数据时它也非常有用。集成C、C++和FORTRAN工具是一件幸事。它还提供了许多傅里叶变换 (FT) 和线性代数函数。 为什么使用 NumPy 而不是列…...
Pycharm与Anaconda Python的开发环境搭建
目录 一:下载 二:安装python 三:设置Pycharm 一:下载 下载Anaconda: Anaconda | The World’s Most Popular Data Science Platform 安装好以后,设置一下环境变量: 打开命令行,…...
2.创建小程序
创建 在开发工具中,选择小程序,点击加号 填写小程序信息,模板使用的是TS+Sass 编辑器的工作区 目录结构 项目使用的是ts的模板,目录结构和js的有一点差异,目录结构如下: miniprogram:小程序根目录 —pages:小程序页面目录 ——xxx:页面目录,一个页面对应一个目…...
XenDesktop5.6如何连接数据库
Citrix在数据库的连接方式上一直不统一,但是也还是有迹可循的。 经过了好长时间的下载以后,今天终于有时间来测试一下最新版本的XenDesktop 5 SP1,由于结合了其他组件和环境的需要,所以,选择了独立部署数据库…...
OAuth2.0一 Spring Security OAuth2.0
这里主讲OAuth2.0 学习OAuth2前提: 掌握Spring Security Spring Security学习 一 OAuth2.0介绍 OAuth(Open Authorization)是一个关于授权(authorization)的开放网络标准,允许用户授权第三方应用访问他们…...
【linux】kernel编译时相关报错
文章目录 1. 问题现象及解决方式1. cc1: error: code model kernel does not support PIC mode 1. 问题现象及解决方式 1. cc1: error: code model kernel does not support PIC mode 问题:编译module时报错 原因:未指定交叉编译器,导致默认…...
C语言 功能型API --------------------strcat()
NAME strcat, strncat - concatenate two strings 头文件 SYNOPSIS #include <string.h> 函数原型: char *strcat(char *dest, const char *src); 功能: 在字符串dest的末尾将字符串src拼接上去 #include <stdio.h> #inc…...
企业展示小程序搭建指南
如今,移动互联网的快速发展,为企业展示提供了更多元化的方式。除了传统的宣传手段,企业还可以借助小程序来展示自己的实力和产品。本文将为大家介绍一种简单的DIY小程序的方法,让企业能够快速打造自己的展示平台。 首先࿰…...
大模型技术实践(一)|ChatGLM2-6B基于UCloud UK8S的创新应用
近半年来,通过对多款主流大语言模型进行了调研,我们针对其训练方法和模型特点进行逐一分析,方便大家更加深入了解和使用大模型。本文将重点分享ChatGLM2-6B基于UCloud云平台的UK8S实践应用。 01各模型结构及特点 自从2017年6月谷歌推出Transf…...
Flink状态和状态管理
1.什么是状态 官方定义:当前计算流程需要依赖到之前计算的结果,那么之前计算的结果就是状态。 这句话还是挺好理解的,状态不只存在于Flink,也存在生活的方方面面,比如看到一个认识的人,如何识别认识呢&am…...
【3Ds Max】布料命令的简单使用
简介 在3ds Max中,"布料"(Cloth)是一种模拟技术,用于模拟物体的布料、织物或软体的行为,例如衣物、帆布等。通过应用布料模拟,您可以模拟出物体在重力、碰撞和其他外力作用下的变形和动态效果。…...
用 VB.net,VBA 两种方式 读取单元格内的 换行数据,并出力到 CSV文件
用 VB.net,VBA 两种方式 读取单元格内的 换行数据,并出力到 CSV文件 需求 如下图所示,为了生成csv文件导入数据库,需要将下图 的 1 和 2 拼接成 如下 3 所示的一行数据, 开头为 1 ,往后为 2 的换行数据 将换…...
kafka线上问题优化
如何防止消息丢失 生产者: 使用同步发送把ack设成1或者all(非0,0可能会出现消息丢失的情况),并且设置同步的分区数>2 消费者:把自动提交改成手动提交 如何防止重复消费 在防止消息丢失的方案中&#…...
FifthOne:用于矢量搜索的计算机视觉接口
一、说明 数据太多了。数据湖和数据仓库;广阔的像素牧场和充满文字的海洋。找到正确的数据就像大海捞针一样!如果你喜欢开源机器学习库 FiftyOne,矢量搜索引擎通过将复杂数据(图像的原始像素值、文本文档中的字符)转换为称为嵌入矢…...
认识Axios
axios中文网 一. 为什么会诞生Axios 最初浏览器页面向服务器请求数据时,返回的是整个页面,整个页面都会刷新ajax的出现,它可以在页面无刷新的情况下请求数据原生的XMLHttpRequest,jQuery封装的ajax,以及axios都可以实…...
系统架构设计专业技能 · 信息安全技术
系列文章目录 系统架构设计专业技能 网络技术(三) 系统架构设计专业技能 系统安全分析与设计(四)【系统架构设计师】 系统架构设计高级技能 软件架构设计(一)【系统架构设计师】 系统架构设计高级技能 …...
kafka晋升之路-理论+场景
kafka晋升之路 一:故事背景二:核心概念2.1 系统架构2.2 生产者(Producer)2.2.1 生产者分区2.2.2 生产者分区策略 2.3 经纪人(Broker)2.3.1 主题(Topic)2.3.2 分区(Partit…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...
企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...
