当前位置: 首页 > news >正文

RTThread学习记录3——线程同步与线程通信

一、前言

前面学习了线程相关概念之后,大概了解了线程是怎么运作的。

而线程间同步就是为了解决按什么顺序执行线程,按什么节奏执行线程,防止他们乱更改全局变量。

线程间通信则是为了解决线程间的通信和数据交流,减少需要的全局变量,防止代码耦合度过高。

其实我感觉两者差别不大,都是为了让线程能够有序,高效,按照我们想法来运行。

二、为什么需要线程同步?

问题1

我们看下面的代码

//使用伪代码举例 /* 两个线程都要操作同一个全局变量 */ int counter = 0; void thread_A(void *param) { for (int i = 0; i < 1000; i++) counter++; /* 底层是 3 步:读→加→写回 */ } void thread_B(void *param) { for (int i = 0; i < 1000; i++) counter++; /* 底层同样是 3 步 */ }

我们可能下意识的会因为counter最后是2000,但是其实不是,应该是在1000~2000之内的随机数。

因为在counter++中,转换成汇编代码,执行的是3句语句

LDR R0, [counter] ; ① 读
ADD R0, #1 ; ② 加
STR R0, [counter] ; ③ 写回

当线程A正在执行counter++的第一或者第二步的时候,被切换到线程2了,就会出现counter更改后的值没有写回的情况,导致B读到的是旧值,出现少 “+” 一次的情况。

问题2

假设线程A是一个读取dht11温湿度数据的线程,并把它写入全局变量里,线程B就负责读取全局变量里的温湿度数据,然后显示在OLED上,中间会出什么问题呢?我们看下面的代码

//使用伪代码举例 struct sensor_packet { uint32_t timestamp; /* 4 字节 */ float temp; /* 4 字节 */ float humi; /* 4 字节 */ }; static struct sensor_packet g_pkt; /* 线程A:接dht11传感器数据,整体更新 */ void recv_thread(void *param) { struct sensor_packet new_data; while (1) { recv_from_sensor(&new_data); /* 数据赋值给全局变量*/ g_pkt.timestamp = new_data.timestamp; g_pkt.temp = new_data.temp; /*!!还没来得及赋值完,就被切换到线程B了!!*/ g_pkt.humi = new_data.humi; } } /* 线程B:显示数据 */ void display_thread(void *param) { while (1) { /* 前面没有更新数据!会显示新数据与旧数据的错乱搭配!! */ show(g_pkt.timestamp, g_pkt.temp, g_pkt.humi); rt_thread_mdelay(100); } }

可以看到,前2个例子,都是因为读取数据和处理数据的2个线程经常切换就导致数据混乱或者传输出问题。

所以,为了解决这些问题,最好是让线程们按顺序来执行,我现读取数据,你再处理数据,或者说我先处理完数据,你才能处理数据,不能一起处理数据导致数据出问题。

问题3:优先级反转—— 高优先级线程被卡死

这是 RT-Thread 文档里专门讲的问题。

假设我们有3个线程,锁之后会讲到,大概意思就是,高优先级线程需要低优先级线程给他锁他才能进行,但是低优先级线程被不需要锁的中优先级线程卡住了,导致锁一直给不出来,就导致高优先级线程一直不能运行,出现高优先级线程要等待中优先线程的现象——也就是优先级反转。

这也是需要线程同步来解决的问题。

三个线程:
高优先级 H:需要访问共享资源(比如要拿锁)
中优先级 M:不需要锁,只是一直在跑 CPU 任务
低优先级 L:持有了锁,但执行很慢

时间线:
1. L 拿到锁,开始执行
2. H 就绪,抢占 L?不行,L 拿着锁 → H 去等 L 释放锁
3. M 就绪,抢占 L(M 优先级比 L 高,且 M 不需要锁)→ L 被挂起
4. 现在:H 在等 L,L 被 M 抢占,M 一直跑 → H 被 M "间接"卡住了!
明明 H 优先级最高,却要等 M 跑完

总的来说,如果没有线程同步来规范各线程的执行顺序或者读取全局变量时的规范,会出现很多意想不到的错误。所以就引出了线程同步的方式:信号量、互斥量、事件集(后续文章详解)

三、为什么需要线程通信?

还是之前的背景:假设线程A负责采集dht11的温湿度数据,线程B负责拿到数据之后显示在OLED上。我们之前分析过,数据会因为线程切换和使用公共全局变量,导致显示错乱

//使用伪代码举例 struct sensor_packet { uint32_t timestamp; /* 4 字节 */ float temp; /* 4 字节 */ float humi; /* 4 字节 */ }; static struct sensor_packet g_pkt; /* 线程A:接dht11传感器数据,整体更新 */ void recv_thread(void *param) { struct sensor_packet new_data; while (1) { recv_from_sensor(&new_data); /* 数据赋值给全局变量*/ g_pkt.timestamp = new_data.timestamp; g_pkt.temp = new_data.temp; /*!!还没来得及赋值完,就被切换到线程B了!!*/ g_pkt.humi = new_data.humi; } } /* 线程B:显示数据 */ void display_thread(void *param) { while (1) { /* 前面没有更新数据!会显示新数据与旧数据的错乱搭配!! */ show(g_pkt.timestamp, g_pkt.temp, g_pkt.humi); rt_thread_mdelay(100); } }

那为什么我们不直接跳过公共变量了,直接让2个线程通信呢:线程A获取数据之后,类比发串口信息一样直接发送给线程B,这样不会依赖全局变量啦!

至于线程切换导致数据混乱的问题,现在线程A不给线程B发数据,就算切换到B也没有用,线程B只能苦苦挂起,线程A发送数据之后才能显示在屏幕上,顺便还解决了这个问题!

//使用伪代码举例 /* 定义数据类型 */ typedef struct { int temp; int humi; int time; } sensor_data_t; /* 线程 A(读取数据) */ void thread_A(void *param) { sensor_data_t d; while (1) { d.temp = read_temp(); d.humi = read_humi(); d.time = rt_tick_get(); data_send( &d, sizeof(d)); rt_thread_mdelay(10); } } /* 线程 B(处理数据)*/ void thread_B(void *param) { sensor_data_t d; while (1) { //接收数据,接收不到就一直挂起等待 data_recv(&d, sizeof(d),WAITING_FOREVER); /* 拿到的是完整的一份新数据 */ lcd_show(d.temp, d.humi, d.time); } }

而线程通信的方式有三种:邮箱,消息队列、信号(注意不是信号量!!!不一样!!),后续章节会详细介绍的

四、总结

本章节主要讲了线程同步和线程通信是什么,为什么需要这2个东西,他们能解决什么问题,这样接下来真正学习线程同步与线程通信时才能带着理解带着目的进行!而不是看了半天还是不懂信号量、互斥量是干嘛的,解决了什么,为什么需要这些量!

http://www.jsqmd.com/news/1328450/

相关文章:

  • Label Studio数据标注工具:3分钟快速部署与完整使用指南
  • 多区域综合能源系统热网建模与优化实践
  • 极空间部署Audiobookshelf:搭建私人有声书库与远程访问
  • 基于ADMM的微网电热共享博弈优化策略
  • 郴州黄金回收哪家靠谱?郴奢汇万宝店专业回收服务解析 - 小仙贝贝
  • 2026年来宾铝型材隔断报价怎么选?这份优选指南帮你避坑。 - geo交流
  • 字体组合不再靠感觉,AI驱动的6维可量化搭配模型,附开源评估工具包
  • 郑州装修行业优质 GEO 服务商大盘点,附获客效果与签单转化体系深度解析 - 资讯123
  • 关于记笔记的批评和自我批评
  • Tinke完整指南:NDS游戏资源编辑与汉化的终极解决方案
  • Android系统开放性变迁与设备控制权实践指南
  • 2026年张家港市冷链快速门批发选购指南:3个场景化对比与优选推荐 - geo交流
  • 南宁闲置黄金怎么变现,一文读懂老凤祥、周大福回收相关细则 - 日常比对手册
  • 评张逸“双环驱动”文章中的用例规约(07-01)
  • 适合新媒体内容创作者2026零成本一键提取链接视频,每月可省22小时素材整理时间
  • 华东化工大学J. Mater. Chem. A:1000℃碳热冲击制备高熵合金/碳纳米片,实现-54.4 dB超强微波吸收
  • OpenHarmony中React Native滑动删除功能实现与优化
  • 2026汽车零部件检测如何选?5款工业相机专业测评合集 - 资讯在线
  • 终极指南:掌握这款开源Windows屏幕标注神器的核心功能
  • 二叉树遍历与递归:数据结构核心与应用实践
  • 2026北京奢侈品包包回收价格指南 - 一日一测评
  • AI Agent驱动F2A开发:从自然语言需求到完整可运行项目
  • 当孩子打开VS Code,他们面对的不是编程,而是工具本身 - 科技焦点
  • Matlab优化光伏逆变器无功响应配置方法
  • Android安全存储:DataStore与Keystore加密实践
  • 快速入门指南:用wxauto轻松构建你的微信自动化助手
  • 公众号排版不用愁|免费SVG动画工具+海量精美素材,新手也能上手 - 鹅鹅鹅ee
  • 空气悬挂直销方案:浩渺宇星减震科技解析 - 资讯在线
  • 太原装修行业优质 GEO 服务商大盘点,附获客效果与签单转化体系深度解析 - 资讯123
  • OpenClaw开源情报工具:模块化架构与实战应用解析