Linux信号量(32)
文章目录
- 前言
- 一、POSIX 信号量
- 信号量的基础知识
- 信号量的基本操作
- 二、基于环形队列实现生产者消费者模型
- 环形队列
- 单生产单消费模型
- 多生产多消费模型
- 总结
前言
加油,加油!!!
一、POSIX 信号量
信号量的基础知识
互斥、同步 不只能通过 互斥锁、条件变量 实现,还能通过 信号量 sem、互斥锁 实现(出自 POSIX 标准)
「信号量」 的本质就是一个 计数器,能够更细粒度的对临界资源进行管理。
- 申请到资源,计数器 --(P 操作)
- 释放完资源,计数器 ++(V 操作)
「信号量」 的 PV 操作都是原子的,假设将 「信号量」 的值设为 1,用来表示 「生产者消费者模型」 中 阻塞队列 _queue 的使用情况
- 当 sem 值为 1 时,线程可以进行 「生产 / 消费」,sem–
- 当 sem 值为 0 时,线程无法进行 「生产 / 消费」,只能阻塞等待
此时的 「信号量」 只有两种状态:1、0,可以实现类似 互斥锁 的效果,即实现 线程互斥,像这种只有两种状态的信号量称为 「二元信号量」
「信号量」 不止可以用于 互斥,它的主要目的是 描述临界资源中的资源数目,比如我们可以把 阻塞队列 切割成 N 份,初始化 「信号量」 的值为 N,当某一份资源就绪时,sem–,资源被释放后,sem++,如此一来可以像 条件变量 一样实现 同步
- 当 sem == N 时,阻塞队列已经空了,消费者无法消费
- 当 sem == 0 时,阻塞队列已经满了,生产者无法生产
用来实现 互斥、同步 的信号量称为 「多元信号量」
综上所述,在使用 「多元信号量」 访问资源时,需要先申请 「信号量」,只有申请成功了才能进行资源访问,否则会进入阻塞等待,即当前资源不可用
在实现 互斥、同步 时,该如何选择?
结合业务场景进行分析,如果待操作的共享资源是一个整体,比较适合使用 互斥锁+条件变量 的方案,但如果共享资源是多份资源,使用 信号量 就比较方便
其实 「信号量」 的工作机制类似于 买电影票,是一种 预订机制,只要你买到票了,即使你晚点到达电影院,你的位置也始终可用,买到票的本质是将对应的座位进行了预订
对于 「信号量」 的第一层理解:申请信号量实际是一种资源预订机制
只要申请 「信号量」 成功了,就一定可以访问临界资源
如果将 「信号量」 实际带入我们之前写的 「生产者消费者模型」 代码中,是不需要进行资源条件判断的,因为 「信号量」本身就已经是资源的计数器了
对于 「信号量」 的第二层理解:使用信号量时,就已经把资源条件判断转化成了信号量的申请行为
// 生产数据(入队)
void Push(const T& inData)
{// 申请信号量 P操作// ..._queue.push(inData);// ...// 释放信号量 V操作
}
信号量的基本操作
有了之前 互斥锁、条件变量 的使用基础,信号量 的接口学习是释放简单的,依旧是只有四个接口:初始化、销毁、申请、释放
初始化信号量
#include <semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value);
参数1:需要初始化的信号量,sem_t 实际就是一个联合体,里面包含了一个 char 数组,以及一个 long int 成员
typedef union
{char __size[__SIZEOF_SEM_T];long int __align;
} sem_t;
参数2:表示当前信号量的共享状态,传递 0 表示线程间共享,传递 非0 表示进程间共享
参数3:信号量的初始值,可以设置为双元或多元信号量
返回值:初始化成功返回 0,失败返回 -1,并设置错误码
销毁信号量
#include <semaphore.h>int sem_destroy(sem_t *sem);
参数:待销毁的信号量
返回值:成功 0,失败 -1, 并设置错误码
申请信号量(等待信号量)
#include <semaphore.h>int sem_wait(sem_t *sem);int sem_trywait(sem_t *sem);int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
主要使用 sem_wait
参数:表示从哪个信号量中申请
返回值:成功返回 0,失败返回 -1,并设置错误码
其他两种申请方式分别是:尝试申请,如果没有申请到资源,就会放弃申请;每隔一段时间进行申请,即 timeout
释放信号量(发布信号量)
#include <semaphore.h>int sem_post(sem_t *sem);
参数:将资源释放到哪个信号量中
返回值:成功返回 0,失败返回 -1,并设置错误码
这些函数其实还是蛮一目了然的,是干啥有啥用都很清楚,所以事不宜迟我们直接开始上手用信号量实现生产者消费者模型!!!
二、基于环形队列实现生产者消费者模型
环形队列
「生产者消费者模型」 中的交易场所是可更换的,不仅可以使用 阻塞队列,还可以使用 环形队列,所谓的 环形队列 并非 队列,而是用数组模拟实现的 “队列”, 并且它的 判空、判满 比较特殊
如何让 环形队列 “转” 起来?
答案是取模,很简单的
在我之前讲解数据结构的时候,我曾经讲过一个环形队列 判空、判满 的方法:
多开一个空间,head、tail 位于同一块空间中时,表示当前队列为空
当待生产的数据落在 head 指向的空间时,就表示已满
但是今天我们参考阻塞队列,搞一个计数器,当计数器的值为 0 时,表示当前为空,当计数器的值为容量时,表示队列为满
我前面说了,因为 「信号量」 本身就是一个天然的计数器,所以在这里我们也天然使用第二个策略
在 环形队列 中,生产者 和 消费者 关心的资源不一样:生产者只关心是否有空间放数据,消费者只关心是否能从空间中取到数据
除非两者相遇,其他情况下生产者、消费者可以并发运行(同时访问环形队列)
两者错位时正常进行生产消费就好了,但两者相遇时需要特殊处理,也就是处理 空、满 两种情况,这就是 环形队列 的运转模式
以下是DS大人给出的一个比喻,有助于大家理解并掌握
简而言之,这个模型的运作模式就是:
- 环形队列为空时:消费者阻塞,只能由生产者进行生产,生产完商品后,消费者可以消费商品
- 环形队列为满时:生产者阻塞,只能由消费者进行消费,消费完商品后,生产者可以生产商品
- 其他情况:生产者、消费者并发运行,各干各的事,互不影响
所以我们可以使用 「信号量」 标识资源的使用情况,但生产者和消费者关注的资源并不相同,所以需要使用两个 「信号量」 来进行操作
- 生产者信号量:标识当前有多少可用空间
- 消费者信号量:标识当前有多少数据
如果说搞两个 条件变量 是 阻塞队列 的精髓,那么搞两个 信号量 就是 环形队列 的精髓,显然,刚开始的时候,生产者信号量初始值为环形队列的大小,消费者信号量初始值为 0
无论是生产者还是消费者,只有申请到自己的 「信号量」 资源后,才进行 生产 / 消费
比如上图中的 pro_sem 就表示 生产者还可以进行 3 次生产,con_sem 表示 消费者还可以消费 5 次
具体生产者消费者我们还可以参照下面代码来理解:
// 生产者
void Producer()
{// 申请信号量(空位 - 1)sem_wait(&pro_sem);// 生产商品// ...// 释放信号量(商品 + 1)sem_post(&con_sem);
}// 消费者
void Consumer()
{// 申请信号量(商品 - 1)sem_wait(&con_sem);// 消费商品// ...// 释放信号量(空位 + 1)sem_post(&pro_sem);
}
生产者和消费者指向同一个位置时保证线程安全,其他情况保证并发度
单生产单消费模型
我们起手先创建一个环形队列头文件
#pragma once#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <vector>#define NUM 8template<class T>
class RingQueue
{
private://P操作void P(sem_t& s){sem_wait(&s);}//V操作void V(sem_t& s){sem_post(&s);}
public:RingQueue(int cap = NUM): _cap(cap), _p_pos(0), _c_pos(0){_q.resize(_cap);sem_init(&_blank_sem, 0, _cap); //blank_sem初始值设置为环形队列的容量sem_init(&_data_sem, 0, 0); //data_sem初始值设置为0}~RingQueue(){sem_destroy(&_blank_sem);sem_destroy(&_data_sem);}//向环形队列插入数据(生产者调用)void Push(const T& data){P(_blank_sem); //生产者关注空间资源_q[_p_pos] = data;V(_data_sem); //生产//更新下一次生产的位置_p_pos++;_p_pos %= _cap;}//从环形队列获取数据(消费者调用)void Pop(T& data){P(_data_sem); //消费者关注数据资源data = _q[_c_pos];V(_blank_sem);//更新下一次消费的位置_c_pos++;_c_pos %= _cap;}
private:std::vector<T> _q; //环形队列int _cap; //环形队列的容量上限int _p_pos; //生产位置int _c_pos; //消费位置sem_t _blank_sem; //描述空间资源sem_t _data_sem; //描述数据资源
};
有一些小细节如下:
- 生产者的信号量初始值为 DEF_CAP
- 消费者的信号量初始值为 0
- 生产者、消费者的起始下标都为 0
在没有 互斥锁 的情况下,是如何 确保生产者与消费者间的互斥关系的?
通过两个 信号量,当两个 信号量 都不为 0 时,双方可以并发操作,这是 环形队列 最大的特点;当 生产者信号量为 0 时,生产者陷入阻塞等待,等待消费者消费;同理当 消费者信号量为 0 时,消费者也会阻塞住,在这里阻塞就是 互斥 的体现。当对方完成 生产 / 消费 后,自己会解除阻塞状态,而这就是 同步
现在我们再来创建 .cpp 文件
#include "test57.hpp"void* Producer(void* arg)
{RingQueue<int>* rq = (RingQueue<int>*)arg;while (true){sleep(1);int data = rand() % 100 + 1;rq->Push(data);std::cout << "Producer: " << data << std::endl;}
}void* Consumer(void* arg)
{RingQueue<int>* rq = (RingQueue<int>*)arg;while (true){sleep(1);int data = 0;rq->Pop(data);std::cout << "Consumer: " << data << std::endl;}
}int main()
{srand((unsigned int)time(nullptr));pthread_t producer, consumer;RingQueue<int>* rq = new RingQueue<int>;pthread_create(&producer, nullptr, Producer, rq);pthread_create(&consumer, nullptr, Consumer, rq);pthread_join(producer, nullptr);pthread_join(consumer, nullptr);delete rq;return 0;
}
结果如下,生产者每隔一秒生产一次、消费者每隔一秒消费一次
剩下的 生产者每隔一秒生产一次 和 消费者每隔一秒消费一次,就交给大家自己去尝试了!
多生产多消费模型
接下来可以实现 多生产多消费场景 中的 CP 模型了,多生产多消费无非就是增加了 消费者与消费者、生产者与生产者 间的 互斥 关系,加锁就行了,现在问题是加几把锁?
答案是 两把,因为当前的 生产者和消费者 关注的资源不一样,一个关注剩余空间,另一个关注是否有商品,一把锁是无法锁住两份不同资源的,所以需要给 生产者、消费者 各配一把锁
阻塞队列 中为什么只需要一把锁?
因为阻塞队列中的共享资源是一整个队列,生产者和消费者访问的是同一份资源,所以一把锁就够了
#pragma once#include <vector>
#include <mutex>
#include <semaphore.h>#define DEF_CAP 10template<class T>
class RingQueue
{
public:RingQueue(size_t cap = DEF_CAP):_cap(cap), _pro_step(0), _con_step(0){_queue.resize(_cap);// 初始化信号量sem_init(&_pro_sem, 0, _cap);sem_init(&_con_sem, 0, 0);// 初始化互斥锁pthread_mutex_init(&_pro_mtx, nullptr);pthread_mutex_init(&_con_mtx, nullptr);}~RingQueue(){// 销毁信号量sem_destroy(&_pro_sem);sem_destroy(&_con_sem);// 销毁互斥锁pthread_mutex_destroy(&_pro_mtx);pthread_mutex_destroy(&_con_mtx);}// 生产商品void Push(const T &inData){// 申请信号量P(&_pro_sem);Lock(&_pro_mtx);// 生产_queue[_pro_step++] = inData;_pro_step %= _cap;UnLock(&_pro_mtx);// 释放信号量V(&_con_sem);}// 消费商品void Pop(T *outData){// 申请信号量P(&_con_sem);Lock(&_con_mtx);// 消费*outData = _queue[_con_step++];_con_step %= _cap;UnLock(&_con_mtx);// 释放信号量V(&_pro_sem);}private:void P(sem_t *sem){sem_wait(sem);}void V(sem_t *sem){sem_post(sem);}void Lock(pthread_mutex_t *lock){pthread_mutex_lock(lock);}void UnLock(pthread_mutex_t *lock){pthread_mutex_unlock(lock);}private:std::vector<T> _queue;size_t _cap;sem_t _pro_sem;sem_t _con_sem;size_t _pro_step; // 生产者下标size_t _con_step; // 消费者下标pthread_mutex_t _pro_mtx;pthread_mutex_t _con_mtx;
};
细节: 加锁行为放在信号量申请成功之后,可以提高并发度
以下又是DS大人给的比喻来帮助你理解~
原因在于,信号量的操作是原子的,这就好比一群学生在进行座位编排,可以先放一个学生进入教室,再给他确定座位;也可以先给每个人确定好自己的座位(一人一座),然后排队进入教室,对号入座即可。先申请 「信号量」 相当于先确定座位,避免进入教室(加锁)后还得选座位
加锁意味着串行化,一定会降低效率,但因为 「信号量」 的操作是原子的,可以确保线程安全,也就不需要加锁保护;也就是可以并发申请 「信号量」,再串行化访问临界资源
// ...int main()
{// 种种子srand((size_t)time(nullptr));// 创建一个阻塞队列RingQueue<int>* rq = new RingQueue<int>;// 创建多个线程(生产者、消费者)pthread_t pro[10], con[20];for(int i = 0; i < 10; i++)pthread_create(pro + i, nullptr, Producer, rq);for(int i = 0; i < 20; i++)pthread_create(con + i, nullptr, Consumer, rq);for(int i = 0; i < 10; i++)pthread_join(pro[i], nullptr);for(int i = 0; i < 20; i++)pthread_join(con[i], nullptr);delete rq;return 0;
}
对比阻塞队列,我们还要思考以下环形队列的意义在哪里
对缓冲区的操作对于计算机说就是小 case,需要关注的点在于 获取数据和消费数据,这是比较耗费时间的,阻塞队列 至多支持获取 一次数据获取 或 一次数据消费,在代码中的具体体现就是 所有线程都在使用一把锁,并且每次只能 push、pop 一个数据;而 环形队列 就不一样了,生产者、消费者 可以通过 条件变量 知晓数据获取、数据消费次数,并且由于数据获取、消费操作没有加锁,支持并发,因此效率十分高
环形队列 中允许 N 个生产者线程一起进行数据获取,也允许 N 个消费者线程一起进行数据消费,简单任务处理感知不明显,但复杂任务就不一样了,这就有点像同时下载多份资源,是可以提高效率的
不过存在即合理,阻塞队列肯定也有其合理的地方,有其更适合的地方,具体问题就具体分析!!!
总结
加油加油!!!再撑住一会儿~
相关文章:

Linux信号量(32)
文章目录 前言一、POSIX 信号量信号量的基础知识信号量的基本操作 二、基于环形队列实现生产者消费者模型环形队列单生产单消费模型多生产多消费模型 总结 前言 加油,加油!!! 一、POSIX 信号量 信号量的基础知识 互斥、同步 不只…...

技术视界 | 打造“有脑有身”的机器人:ABC大脑架构深度解析(上)
ABC大脑架构:连接大模型与物理世界的具身智能新范式 在具身智能和类人机器人技术快速发展的背景下,如何高效整合“大模型的认知理解能力”与“对真实物理世界的精准控制”,成为当前智能体系统设计中最具挑战性也是最关键的问题之一。尽管大语…...

使用堡塔和XShell
使用堡塔和XShell 一、SSH协议介绍 SSH为SecureShell的缩写,由IETF的网络小组(NetworkWorkingGroup)所制定;SSH为建立在应用层基础上的安全协议。SSH是较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用SSH协议可以有效防止远程管理过程中…...

软件项目交付阶段,验收报告记录了什么?有哪些标准要求?
软件项目交付阶段,验收报告扮演着至关重要的角色,它相当于一份详尽的“成绩单”,具体记录了项目完成的具体情况以及是否达到了既定的标准。 项目基本信息 该环节将展示软件项目的核心信息,包括项目名称、开发团队构成、项目实施…...

LightGBM的python实现及参数优化
文章目录 1. LightGBM模型参数介绍2. 核心优势3. python实现LightGBM3.1 基础实现3.1.1 Scikit-learn接口示例3.1.2 Python API示例 3.2 模型调优3.2.1 GridSearchCV简介3.2.2 LightGBM超参调优3.2.3 GridSearchCV寻优结果解读 在之前的文章 Boosting算法【AdaBoost、GBDT 、X…...

封装渐变堆叠柱状图组件附完整代码
组件功能 这是一个渐变堆叠柱状图组件,主要功能包括: 在一根柱子上同时显示高、中、低三种危险级别数据使用渐变色区分不同危险级别(高危红色、中危橙色、低危蓝色)悬停显示详细数据信息(包括总量和各级别数据&#…...
分布式项目保证消息幂等性的常见策略
Hello,大家好,我是灰小猿! 在分布式系统中,由于各个服务之间独立部署,各个服务之间依靠远程调用完成通信,再加上面对用户重复点击时的重复请求等情况,所以如何保证消息消费的幂等性是在分布式或…...

山东大学软件学院创新项目实训开发日志——第十三周
目录 1.开展prompt工程,创建个性化AI助理,能够基于身份实现不同角度和语言风格的回答。 2.对输出进行格式化,生成特定格式的会议计划文档。 3.学习到的新知识 本阶段我所做的工作 1.开展prompt工程,创建个性化AI助理ÿ…...
如何在sublime text中批量为每一行开头或者结尾添加删除指定内容
打开你的文件:首先,在 Sublime Text 中打开你想要编辑的文件,然后全选 行首插入: 选择所有行的开头: 使用快捷键 Ctrl Shift L(Windows/Linux)或 Cmd Shift L(Mac)&…...

Cesium 透明渐变墙 解决方案
闭合路径修复 通过增加额外点确保路径首尾相接 透明渐变效果 使用RGBA颜色模式实现从完全不透明到完全透明的平滑渐变 参数可调性 提供多个可调参数,轻松自定义颜色、高度和圆环尺寸 完整代码实现 <!DOCTYPE html> <html> <head><meta …...
网络原理与 TCP/IP 协议详解
一、网络通信的本质与基础概念 1.1 什么是网络通信? 网络通信的本质是跨设备的数据交换,其核心目标是让不同物理位置的设备能够共享信息。这种交换需要解决三个核心问题: 如何定位设备? → IP地址如何找到具体服务?…...

day022-定时任务-故障案例与发送邮件
文章目录 1. cron定时任务无法识别命令1.1 故障原因1.2 解决方法1.2.1 对命令使用绝对路径1.2.2 在脚本开头定义PATH 2. 发送邮件2.1 安装软件2.2 配置邮件信息2.3 巡检脚本与邮件发送2.3.1 巡检脚本内容2.3.2 制作时任务发送邮件 3. 调取API发送邮件3.1 编写文案脚本3.2 制作定…...

新增 git submodule 子模块
文章目录 1、基本语法2、添加子模块后的操作3、拉取带有submodule的仓库 git submodule add 是 Git 中用于将另一个 Git 仓库作为子模块添加到当前项目中的命令。 子模块允许你将一个 Git 仓库作为另一个 Git 仓库的子目录,同时保持它们各自的提交历史独立。 1、基…...

List优雅分组
一、前言 最近小永哥发现,在开发过程中,经常会遇到需要对list进行分组,就是假如有一个RecordTest对象集合,RecordTest对象都有一个type的属性,需要将这个集合按type属性进行分组,转换为一个以type为key&…...

Linux 使用 Docker 安装 Milvus的两种方式
一、使用 Docker Compose 运行 Milvus (Linux) 安装并启动 Milvus Milvus 在 Milvus 资源库中提供了 Docker Compose 配置文件。要使用 Docker Compose 安装 Milvus,只需运行 wget https://github.com/milvus-io/milvus/releases/download/v2.5.10/milvus-standa…...

AR眼镜+AI视频盒子+视频监控联网平台:消防救援的智能革命
在火灾现场,每一秒都关乎生死。传统消防救援方式面临信息滞后、指挥盲区、环境复杂等挑战。今天,一套融合AR智能眼镜AI视频分析盒子智能监控管理平台的"三位一体"解决方案,正在彻底改变消防救援的作业模式,为消防员装上…...

编程技能:字符串函数10,strchr
专栏导航 本节文章分别属于《Win32 学习笔记》和《MFC 学习笔记》两个专栏,故划分为两个专栏导航。读者可以自行选择前往哪个专栏。 (一)WIn32 专栏导航 上一篇:编程技能:字符串函数09,strncmp 回到目录…...

使用tunasync部署企业内部开源软件镜像站-Centos Stream 9
使用tunasync部署企业内部开源软件镜像站 tunasync 是清华大学 TUNA 镜像源目前使用的镜像方案,本文将介绍如何使用 tunasync 部署企业内部开源软件镜像站。 基于tunasync mirror-web nginx进行镜像站点搭建。 1. tunasync设计 tunasync架构如下: …...
c/c++的opencv像素级操作二值化
图像级操作:使用 C/C 进行二值化 在数字图像处理中,图像级操作 (Image-Level Operations) 是指直接在图像的像素级别上进行处理,以改变图像的视觉特性或提取有用信息。这些操作通常不依赖于图像的全局结构,而是关注每个像素及其邻…...

C++----Vector的模拟实现
上一节讲了string的模拟实现,string的出现时间比vector靠前,所以一些函数给的也比较冗余,而后来的vector、list等在此基础上做了优化。这节讲一讲vector的模拟实现,vector与模板具有联系,而string的底层就是vector的一…...

Mac redis下载和安装
目录 1、官网:https://redis.io/ 2、滑到最底下 3、下载资源 4、安装: 5、输入 sudo make test 进行编译测试 会提示 编辑 6、sudo make install 继续 7、输入 src/redis-server 启动服务器 8、输入 src/redis-cli 启动测试端 1、官网ÿ…...

[25-cv-05718]BSF律所代理潮流品牌KAWS公仔(商标+版权)
潮流品牌KAWS公仔 案件号:25-cv-05718 立案时间:2025年5月21日 原告:KAWS, INC. 代理律所:Boies Schiller Flexner LLP 原告介绍 原告是一家由美国街头艺术家Brian Donnelly创立的公司,成立于2002年2月25日&…...
【PhysUnits】9 取负重载(negation.rs)
一、源码 这段代码是类型级二进制数(包括正数和负数)的取反和取负操作。它使用了类型系统来表示二进制数,并通过特质(trait)和泛型来实现递归操作。 use super::basic::{B0, B1, Z0, N1}; use core::ops::Neg;// 反…...

深度思考、弹性实施,业务流程自动化的实践指南
随着市场环境愈发复杂化,各类型企业的业务步伐为了跟得上市场节奏也逐步变得紧张,似乎只有保持极强的竞争力、削减成本、提升抗压能力才能在市场洪流中博得一席之位。此刻企业需要制定更明智的解决方案,以更快、更准确地优化决策流程。与简单…...

UWB:litepoint获取txquality里面的NRMSE
在使用litepoint测试UWB,获取txquality里面的NRMSE时,网页端可以正常获取NRMSE。但是通过SCPI 命令来获取NRMSE一直出错。 NRMSE数据类型和pyvisa问题: 参考了user guide,发现NRMSE的数值是ARBITRARY_BLOCK FLOAT,非string。 pyvisa无法解析会返回错误。 查询了各种办法…...

VUE npm ERR! code ERESOLVE, npm ERR! ERESOLVE could not resolve, 错误有效解决
VUE : npm ERR! code ERESOLVE npm ERR! ERESOLVE could not resolve 错误有效解决 npm install 安装组件的时候出现以上问题,npm版本问题报错解决方法:用上述方法安装完成之后又出现其他的问题 npm install 安装组件的时候出现以上问题&…...

IoT/HCIP实验-1/物联网开发平台实验Part1(快速入门,MQTT.fx对接IoTDA)
文章目录 实验介绍设备接入IoTDA进入IoTDA平台什么是IoTDA 开通服务创建产品和设备定义产品模型(Profile)设备注册简思(实例-产品-设备) 模拟.与平台通信虚拟设备/MQTT.fx应用 Web 控制台QA用户或密码错误QA证书导致的连接失败设备与平台连接成功 上报数…...

DMA STM32H7 Domains and space distrubution
DMA这个数据搬运工,对谁都好,任劳任怨,接受雇主设备的数据搬运业务。每天都忙碌着!哈哈哈。 1. DMA 不可能单独工作,必须接收其他雇主的业务,所以数据搬运业务的参与者是DMA本身和业务需求发起者。 2. 一…...

洪水危险性评价与风险防控全攻略:从HEC-RAS数值模拟到ArcGIS水文分析,一键式自动化工具实战,助力防洪减灾与应急管理
🔍 洪水淹没危险性是洪水损失评估、风险评估及洪水应急和管理规划等工作的重要基础。当前,我国正在开展的自然灾害风险普查工作,对洪水灾害给予了重点关注,提出了对洪水灾害危险性及风险评估的明确要求。洪水危险性及风险评估通常…...
Gemini Pro 2.5 输出
好的,我已经按照您的要求,将顶部横幅提示消息修改为右下角的 Toast 样式通知。 以下是涉及更改的文件及其内容: 1. my/src/html-ui.js 移除了旧的 #message-area div。在 <body> 底部添加了新的 #toast-container div 用于存放 Toas…...