S1-07 事件组

事件组

在 FreeRTOS 中,事件组(Event Group)是一种用于任务间通信的机制,用于在多个任务之间同步和传递事件。
事件组主要包含一下两个概念:

  1. 事件标志位(Event Flags):每个事件标志位代表一个独立的事件状态。
  2. 事件组控制块(Event Group Control Block,EGCB):事件组的信息结构体,维护了事件标志位的状态等信息。

通过调用 FreeRTOS 提供的 API 函数,任务可以进行如下操作:

  1. 设置指定的事件标志位为已发生。
  2. 查询事件标志位是否已经被设置。
  3. 等待多个事件标志位中的任意一个或全部都被设置。

对于等待事件标志位的任务,可以选择在等待时阻塞自己等待事件的发生,也可以在等待时设置超时时间,如果超时仍然没有事件发生,则任务会自动解除阻塞并返回相应值。

事件组由 EventGroupHandle_t 类型的变量引用。
如果 configUSE_16_BIT_TICKS 如果 configUSE_16_BIT_TICKS 设置为 0,则为 24。configUSE_16_BIT_TICKS 的值取决于 任务内部实现中用于线程本地存储的数据类型。
事件组中的所有事件位都 存储在 EventBits_t 类型的单个无符号整数变量中。 事件位 0 存储在位 0 中, 事件位 1 存储在位1 中,依此类推。
下图表示一个 24 位事件组, 使用 3 个位来保存前面描述的 3 个示例事件。 在图片中,仅设置了 事件位 2。
在这里插入图片描述

第一个餐厅的厨师(作为事件标志位)

回到我们厨子和吃货的世界中,本次出场的只有厨子,另外还有一些服务员,服务员负责给厨子配菜,这时候厨子做一个汉堡需要等待三样东西,分别是面包、肉饼、蔬菜,做蔬菜的服务员等肉饼做好后再做蔬菜,做肉饼的则要等待做面包的,而做面包的需要等待厨子的号令,一切是那么的竟然有序。

代码共享位置:https://wokwi.com/projects/362876432326851585

#define START_FLAG      (1 << 0)
#define BREAD_FLAG      (1 << 1)
#define MEAT_FLAG       (1 << 2)
#define VEGETABLE_FLAG  (1 << 3)
EventGroupHandle_t xEventHamburg = NULL;  // 做汉堡的事件组
// 面包服务员
void waiter_bread_task(void *param_t){EventBits_t uxBits;uxBits = xEventGroupWaitBits(xEventHamburg, // 事件组句柄START_FLAG,     // 等待开始事件pdTRUE,         // 读取后清空标志位pdTRUE,         // ADN关系,1个值无所谓portMAX_DELAY);printf("[BRED] 等到开始事件 : %X\n", uxBits);vTaskDelay(pdMS_TO_TICKS(random(500,2000)));uxBits = xEventGroupSetBits(xEventHamburg, BREAD_FLAG); // 设置面包标志位printf("[BRED] 面包已就绪 : %X\n", uxBits);vTaskDelete(NULL);
}
// 肉饼服务员
void waiter_meat_task(void *param_t){EventBits_t uxBits;uxBits = xEventGroupWaitBits(xEventHamburg, // 事件组句柄BREAD_FLAG,     // 等待面包事件pdFALSE,        // 读取后不清空pdTRUE,         // ADN关系,1个值无所谓portMAX_DELAY);printf("[MEAT] 等到面包做好 : %X\n", uxBits);vTaskDelay(pdMS_TO_TICKS(random(500,2000)));uxBits = xEventGroupSetBits(xEventHamburg, MEAT_FLAG); // 设置肉饼标志位printf("[MEAT] 肉饼已就绪 : %X\n", uxBits);vTaskDelete(NULL);
}
// 蔬菜服务员
void waiter_vegetable_task(void *param_t){EventBits_t uxBits;uxBits = xEventGroupWaitBits(xEventHamburg, // 事件组句柄MEAT_FLAG,      // 等待开始事件pdFALSE,        // 读取后不清空pdTRUE,         // ADN关系,1个值无所谓portMAX_DELAY);printf("[VEGE] 等到肉饼做好 : %X\n", uxBits);vTaskDelay(pdMS_TO_TICKS(random(500,2000)));uxBits = xEventGroupSetBits(xEventHamburg, VEGETABLE_FLAG); // 设置蔬菜标志位printf("[VEGE] 蔬菜已就绪 : %X\n", uxBits);vTaskDelete(NULL);
}
// 厨师线程
void chef_task(void *param_t){pinMode(4, INPUT_PULLUP);while(1){if(digitalRead(4) == LOW){// 开始做汉堡EventBits_t uxBits;  // 设置事件标志位的返回值uxBits = xEventGroupSetBits(xEventHamburg, START_FLAG);  // 这个返回值有可能会清空标志位,具体读文档printf("[CHEF] 开始做汉堡 : %X\n", uxBits);uxBits = xEventGroupWaitBits(xEventHamburg,                            // 事件句柄BREAD_FLAG | MEAT_FLAG | VEGETABLE_FLAG,    // 等待代表面包、肉饼、蔬菜的标志位pdFALSE,                                    // 是否清空对应标志位pdTRUE,                                     // 等待的Bits判断关系 True为 AND, False为 ORportMAX_DELAY);                             // 等待超时时间printf("[CHEF] 汉堡做完了 : %X\n", uxBits);// 重置事件组xEventGroupClearBits(xEventHamburg, START_FLAG | BREAD_FLAG | MEAT_FLAG | VEGETABLE_FLAG);uxBits = xEventGroupGetBits(xEventHamburg);printf("[CHEF] 汉堡做好了,我下班了 : %X\n", uxBits);vTaskDelete(NULL);}vTaskDelay(100);}
}
void setup() {Serial.begin(115200);xEventHamburg = xEventGroupCreate();  //初始化事件组// 启动各个线程xTaskCreate(chef_task, "Chef", 1024*2, NULL, 1, NULL);xTaskCreate(waiter_bread_task, "Bread", 1024*2, NULL, 1, NULL);xTaskCreate(waiter_meat_task, "Meat", 1024*2, NULL, 1, NULL);xTaskCreate(waiter_vegetable_task, "Vegetable", 1024*2, NULL, 1, NULL);
}
void loop() {delay(100);
}

代码中首先在setup中通过 xEventGroupCreate 创建了一个事件组。
chef_task 中不断判断是否按下了按钮,当按钮被按下时,首先设置事件组的第一位为1,表示开始信号,此时的3个字节数据为(此时事件组值为1)
在这里插入图片描述

设置完之后,就开始等待第2 3 4位的数据,这里我们用的都是 xEventGroupWaitBits 等待,该函数一共有5个参数,函数原型如下:

EventBits_t xEventGroupWaitBits(const EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToWaitFor,const BaseType_t xClearOnExit,const BaseType_t xWaitForAllBits,TickType_t xTicksToWait );

xEventGroup : 事件组句柄
uxBitsToWaitFor : 指定事件组中要测试的一个或多个事件位的按位值,可以用 | 运算指定多个,例如,等待第0位则为1,等待第二位则为2,等待第三位则为4,等待第四位则为8,如果等待第1位和第三位,则为1|3=5。
xClearOnExit : 是否清除时间位,设置为 pdTRUE, 那么在作为 uxBitsToWaitFor 参数传递的值中设置的任何位 会在 xEventGroupWaitBits() 返回某个值之前在事件组中清除掉, 前提是 xEventGroupWaitBits() 因超时以外的原因而返回值。
xWaitForAllBits
用于创建逻辑与测试 (必须设置所有位)或逻辑或测试(必须设置一个 或多个位),如下所示:如果 xWaitForAllBits 设置为 pdTRUE, 那么当在作为 uxBitsToWaitFor 参数传递的值中设置的所有位 均已在事件组中设置好,或指定的阻塞时间已过期,则 xEventGroupWaitBits() 会返回相应值。如果 xWaitForAllBits 设置为 pdFALSE,那么当在作为 uxBitsToWaitFor 参数传递的值中设置的任何位已在事件组中设置好, 或指定的阻塞时间已过期,则 xEventGroupWaitBits() 会返回相应值。
xTicksToWait : 超时时间。
这里我们等待选择不清空,并且使用同时等待第2、3、4位事件都到达。
xEventGroupSetBits 函数有个返回值,是当前事件组的值,但需要注意的是,因为执行完该函数后,系统调度器会对任务进行一次调度,看是否有任务等到了某个事件,如果有事件被触发,根据任务优先级,会先执行另外的事件,在返回。
因为在 waiter_bread_task 任务中等待该标志位的时候选择了清空标志位,所以这时候获得的返回值为0,其实我们已经设置了 START_FLAG 标志位,可以通过修改 waiter_bread_task 任务中 xEventGroupWaitBits 函数的 xClearOnExit 测试。

待三个事件都到达时,使用 xEventGroupClearBits 清空我们使用过的前四位。

waiter_bread_task 任务中,首先等待 START_FLAG 时间到达,也就是第一位置1,这里的等待函数 xClearOnExit 参数位 pdTRUE,表示当收到这个信号后将立刻清空。
此时任务组数据如下(此时事件组值为0):
在这里插入图片描述

然后通过 xEventGroupSetBits 函数设置第二位为1(此时事件组值为2):
在这里插入图片描述

waiter_meat_task 任务启动后一直等待 BREAD_FLAG 事件到达,当事件到达后,通过 xEventGroupSetBits(xEventHamburg, BREAD_FLAG) 设置事件组为6:
在这里插入图片描述

waiter_vegetable_task 任务启动后一直等待 MEAT_FLAG 事件到达, 当事件到达后,通过 xEventGroupSetBits(xEventHamburg, VEGETABLE_FLAG) 设置事件组为14(也就是E):
在这里插入图片描述

最后回到 chef_task 任务中,等待三个值凑齐,继续往下执行,通过 xEventGroupClearBits(xEventHamburg, START_FLAG | BREAD_FLAG | MEAT_FLAG | VEGETABLE_FLAG) 清空事件组,此时事件组值再次归零。

标志位的应用方向

事件标志为用于按顺序执行的业务逻辑,举一个智慧物流的例子,机械臂为一辆自动行驶的电动车,装载上货物,电动车开到指定的厂房,中间需要经过一个电动门。机械臂收到指令后触发装货的信号,等装完货之后,机械臂又触发小车的移动信号,等小车移动到门口的时候又触发电动门开闸的信号,然后等电动门开启完毕后,再给小车一个移动的信号,以此类推,直到小车运到下一个厂房机械臂将货物卸下。
这里边用到了多个信号量协同,但是他们之间是有顺序关系的。

二号餐厅的厨师(改进的标志位)

在一号餐厅中,所有服务员必须等上一名服务员完成工作后才会做自己的工作,但本质上面包、肉饼和蔬菜的准备并不是顺序关系,而是并行关系,三个线程完全可以独立运行,他们只需要共同等待一个开始信号,信号到达后各自做自己的工作,而厨师的任务与他们三个是并行的,所以厨师还是需要等待另外三个人工作完成后才能开动。
所以,我们需要对第一个餐厅的代码进行一次修改

代码共享位置:https://wokwi.com/projects/362946353284773889

#define START_FLAG      (1 << 0)
#define BREAD_FLAG      (1 << 1)
#define MEAT_FLAG       (1 << 2)
#define VEGETABLE_FLAG  (1 << 3)
EventGroupHandle_t xEventHamburg = NULL;  // 做汉堡的事件组
// 面包服务员
void waiter_bread_task(void *param_t){EventBits_t uxBits;uxBits = xEventGroupWaitBits(xEventHamburg, // 事件组句柄START_FLAG,     // 等待开始事件pdFALSE,        // 读取后不清空标志位pdTRUE,         // ADN关系,1个值无所谓portMAX_DELAY);printf("[BRED] 等到开始事件 : %X\n", uxBits);vTaskDelay(pdMS_TO_TICKS(random(500,2000)));uxBits = xEventGroupSetBits(xEventHamburg, BREAD_FLAG); // 设置面包标志位printf("[BRED] 面包已就绪 : %X\n", uxBits);vTaskDelete(NULL);
}
// 肉饼服务员
void waiter_meat_task(void *param_t){EventBits_t uxBits;uxBits = xEventGroupWaitBits(xEventHamburg, // 事件组句柄START_FLAG,     // 等待开始事件pdFALSE,        // 读取后不清空标志位pdTRUE,         // ADN关系,1个值无所谓portMAX_DELAY);printf("[MEAT] 等到开始事件 : %X\n", uxBits);vTaskDelay(pdMS_TO_TICKS(random(500,2000)));uxBits = xEventGroupSetBits(xEventHamburg, MEAT_FLAG); // 设置肉饼标志位printf("[MEAT] 肉饼已就绪 : %X\n", uxBits);vTaskDelete(NULL);
}
// 蔬菜服务员
void waiter_vegetable_task(void *param_t){EventBits_t uxBits;uxBits = xEventGroupWaitBits(xEventHamburg, // 事件组句柄START_FLAG,     // 等待开始事件pdFALSE,        // 读取后不清空标志位pdTRUE,         // ADN关系,1个值无所谓portMAX_DELAY);printf("[VEGE] 等到开始事件 : %X\n", uxBits);vTaskDelay(pdMS_TO_TICKS(random(500,2000)));uxBits = xEventGroupSetBits(xEventHamburg, VEGETABLE_FLAG); // 设置蔬菜标志位printf("[VEGE] 蔬菜已就绪 : %X\n", uxBits);vTaskDelete(NULL);
}
// 厨师线程
void chef_task(void *param_t){pinMode(4, INPUT_PULLUP);while(1){if(digitalRead(4) == LOW){// 开始做汉堡EventBits_t uxBits;  // 设置事件标志位的返回值uxBits = xEventGroupSetBits(xEventHamburg, START_FLAG);  // 这个返回值有可能会清空标志位,具体读文档printf("[CHEF] 开始做汉堡 : %X\n", uxBits);uxBits = xEventGroupWaitBits(xEventHamburg,                            // 事件句柄BREAD_FLAG | MEAT_FLAG | VEGETABLE_FLAG,    // 等待代表面包、肉饼、蔬菜的标志位pdFALSE,                                    // 是否清空对应标志位pdTRUE,                                     // 等待的Bits判断关系 True为 AND, False为 ORportMAX_DELAY);                             // 等待超时时间printf("[CHEF] 汉堡做完了 : %X\n", uxBits);// 重置事件组xEventGroupClearBits(xEventHamburg, START_FLAG | BREAD_FLAG | MEAT_FLAG | VEGETABLE_FLAG);uxBits = xEventGroupGetBits(xEventHamburg);printf("[CHEF] 汉堡做好了,我下班了 : %X\n", uxBits);vTaskDelete(NULL);}vTaskDelay(100);}
}
void setup() {Serial.begin(115200);xEventHamburg = xEventGroupCreate();  //初始化事件组// 启动各个线程xTaskCreate(chef_task, "Chef", 1024*2, NULL, 1, NULL);xTaskCreate(waiter_bread_task, "Bread", 1024*2, NULL, 1, NULL);xTaskCreate(waiter_meat_task, "Meat", 1024*2, NULL, 1, NULL);xTaskCreate(waiter_vegetable_task, "Vegetable", 1024*2, NULL, 1, NULL);
}
void loop() {delay(100);
}

这个例程基本和第一个例程是一样的,只是我们等待的标志位发生了改变,让所有的服务员都等待 START_FLAG 的信号,而所有人都不得清空这个信号,如果一旦有一个人清空了,那后面有人就会丢失这个信号。

此类表示为的应用方向

还是以智慧物流为例,我们现在的需求是用一辆车运送三个物资到另外的仓库中,当我们下达指令之后,调取物资的机械手并不是顺序运行的,而是各自运行寻找物资,待物资全部装车后车才会启动。这时候就由运货车(或调度台)发送一个“运送”指令,电动车就为等待,三个机械臂分别寻找和装在三种物资到车上,待所有物资都齐全之后,电动车发车。

三号餐厅的厨师(同步)

当这三种配料都准备齐的时候,厨子才开始制作汉堡,而这时候所有的服务员都已经下班走了,厨师就不爽了,凭什么你们做完都走了,我还在工作!
所以,三号餐厅的厨师给老板提了个建议,说要有些服务员的工作太轻松了,下班太早不利于同事间的团结,所以我建议我们必须等到所有人的工作完成之后大家一起下班。
黑心老板也采纳了这个建议,于是,同步就出现了!

代码共享地址:https://wokwi.com/projects/362947317933844481

#define BURG_FLAG       (1 << 0)
#define BREAD_FLAG      (1 << 1)
#define MEAT_FLAG       (1 << 2)
#define VEGETABLE_FLAG  (1 << 3)
EventGroupHandle_t xEventHamburg = NULL;  // 做汉堡的事件组
// 面包服务员
void waiter_bread_task(void *param_t){EventBits_t uxBits;printf("[BRED] 骂骂咧咧的开始烤面包...\n");vTaskDelay(pdMS_TO_TICKS(random(1000,5000)));printf("[BRED] 面包烤好了,我打算设置标志位!\n");uxBits = xEventGroupSync(xEventHamburg,     // 事件句柄BREAD_FLAG,         // 要设置的标志位BURG_FLAG | BREAD_FLAG | MEAT_FLAG | VEGETABLE_FLAG,  // 同步等待的标志位portMAX_DELAY);     // 超时时间printf("[BRED] 面包已就绪 : %X\n", uxBits);vTaskDelete(NULL);
}
// 肉饼服务员
void waiter_meat_task(void *param_t){EventBits_t uxBits;printf("[MEAT] 叽叽歪歪的开始煎肉饼...\n");vTaskDelay(pdMS_TO_TICKS(random(1000,5000)));printf("[MEAT] 肉饼煎好了,我打算设置标志位!\n");uxBits = xEventGroupSync(xEventHamburg,     // 事件句柄MEAT_FLAG,          // 要设置的标志位BURG_FLAG | BREAD_FLAG | MEAT_FLAG | VEGETABLE_FLAG,  // 同步等待的标志位portMAX_DELAY);     // 超时时间printf("[MEAT] 肉饼已就绪 : %X\n", uxBits);vTaskDelete(NULL);
}
// 蔬菜服务员
void waiter_vegetable_task(void *param_t){EventBits_t uxBits;printf("[VEGE] 哼哼吱吱的做开始洗蔬菜...\n");vTaskDelay(pdMS_TO_TICKS(random(1000,5000)));printf("[VEGE] 蔬菜洗好了,我打算设置标志位!\n");uxBits = xEventGroupSync(xEventHamburg,     // 事件句柄VEGETABLE_FLAG,     // 要设置的标志位BURG_FLAG | BREAD_FLAG | MEAT_FLAG | VEGETABLE_FLAG,  // 同步等待的标志位portMAX_DELAY);     // 超时时间printf("[VEGE] 蔬菜已就绪 : %X\n", uxBits);vTaskDelete(NULL);
}
// 厨师线程
void chef_task(void *param_t){pinMode(4, INPUT_PULLUP);EventBits_t uxBits;while(1){if(digitalRead(4) == LOW){// 开始做汉堡printf("[CHEF] 美滋滋的做开始磨洋工...\n");uxBits = xEventGroupSync(xEventHamburg,     // 事件句柄BURG_FLAG,          // 要设置的标志位BURG_FLAG | BREAD_FLAG | MEAT_FLAG | VEGETABLE_FLAG,  // 同步等待的标志位portMAX_DELAY);     // 超时时间printf("[CHEF] 汉堡做好了,大家可以下班了 : %X\n", uxBits);vTaskDelete(NULL);}vTaskDelay(100);}
}
void setup() {Serial.begin(115200);xEventHamburg = xEventGroupCreate();  //初始化事件组// 启动各个线程xTaskCreate(chef_task, "Chef", 1024*2, NULL, 1, NULL);xTaskCreate(waiter_bread_task, "Bread", 1024*2, NULL, 1, NULL);xTaskCreate(waiter_meat_task, "Meat", 1024*2, NULL, 1, NULL);xTaskCreate(waiter_vegetable_task, "Vegetable", 1024*2, NULL, 1, NULL);
}
void loop() {delay(100);
}

在上面的代码中,我们把原来的 START_FLAG 改成了 BURG_FLAG 表示汉堡就绪的标志位,所有线程启动后就开始忙碌自己的工作了,当忙完之后就把自己的标志位设置成1,同时等待其他四位同时的标志位,只有 chef_task 的线程是等待我们操作按钮的。
如果我们启动后不按按钮,那另外三个线程依然会把自己的事情做完,然后开始等待厨子;
但如果启动后马上按动按钮,那么厨子是会提前把自己的汉堡做完,同时等待另外几个同事的(所以这里的例子在实际生活中可能有些不合理)。
例程中我们用到了一个新的函数,同步函数

EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet,const EventBits_t uxBitsToWaitFor,TickType_t xTicksToWait );

该函数的作用是 将指定标志位设置为1,同时等待其他标志位到达后继续执行。
xEventGroup :事件句柄;
uxBitsToSet :在确定(且可能等待)uxBitsToWait参数指定的所有位 设置完成之前, 要设置事件组中的一个或多个位。
uxBitsToWaitFor:指定事件组中要测试的一个或多个事件位 的按位值。
xTicksToWait:等待时间。
需要注意的是:uxBitsToSet 参数可以是一个位,也可以是是多个位,使用或运算符(|)拼接,但 uxBitsToSet 的值并不一定非得包含在 uxBitsToWaitFor 中。

同步事件组的应用方向

以动车组为例,动车组和普通火车的区别在于,每节车厢都是一个独立的系统,都各自提供动力运行,所以才叫动车组,所以动车组在运行的时候必须等待所有列车就绪的信号才能发车,这样才能最大的节省能量提高效率。
动车组在开车前,先要装在每节车厢的乘客,然后各自关门,当1号车厢的门关闭后,发出一个同步信号,并等待其他车厢就绪,陆续所有车厢的门关闭之后,大家一同出发。
需要注意的是,每节车厢关门就绪的时间是不同的,但最终所有车厢是需要在同一时间发车的,所有车厢是并行的,如果再用标志位的方式,就无法实现了(或者实现起来很啰嗦),所以就用到了 同步 的概念。

关于事件组的所有API,可以参考:https://www.freertos.org/zh-cn-cmn-s/event-groups-API.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/620802.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

动态规划篇-02:杨辉三角

118、杨辉三角 状态转移方程 我们还是老套路起手&#xff1a;先列出状态转移方程。 base case 每行的第一个和最后一个数字都是1 明确状态 “原问题或子问题中变化的变量” 此处的“状态”就是某一位置的数字大小 在此题中&#xff0c;每一个数的“状态” 是由其左上方…

Ensp AR/WLAN设备启动失败问题 错误代码41 解决方案

现象描述 启动AR设备之后&#xff0c;设备命令行无法接收输入&#xff0c;在长时间等待后一直输出“####”。启动AR/WLAN设备时&#xff0c;提示“…错误代码40…”。 检查虚拟网卡设置。 检查安装eNSP的PC上是否存在名为“VirtualBox Host-Only Network”的虚拟网卡。 - 如果…

【conda】pip安装报错,网络延时问题解决记录(亲测有效)

【conda】pip安装报错&#xff0c;网络延时问题解决记录 1. pip install 报错如下所示2. 解决方案&#xff1a; 1. pip install 报错如下所示 pip._vendor.urllib3.exceptions.ReadTimeoutError: HTTPSConnectionPool(hostfiles.pythonhosted.org, port443): Read timed out.…

AI文本生图模型Stable Diffusion部分模型叠加效果

兄弟们,最近有个烦恼,就是找图有点费事,干脆自己部署个文本生图模型Stable Diffusion,虽然机器性能慢,但是效果还可以,先和大家截图分享下,后面将映射ai.shenjian.online供大家免费使用 1. 效果预览 2. 主模型及插件安装 下载模型icerealistic_v21.safetensors放到./models/S…

Leetcode447. 回旋镖的数量

Every day a Leetcode 题目来源&#xff1a;447. 回旋镖的数量 解法1&#xff1a;枚举 哈希 题目所描述的回旋镖可以视作一个 V 型的折线。我们可以枚举每个 points[i]&#xff0c;将其当作 V\texttt{V}V 型的拐点。设 points 中有 m 个点到 points[i] 的距离均相等&#…

学习Java API(一):基础知识点一文通✅

推荐阅读 智能化校园&#xff1a;深入探讨云端管理系统设计与实现&#xff08;一&#xff09; 智能化校园&#xff1a;深入探讨云端管理系统设计与实现&#xff08;二&#xff09; 文章目录 推荐阅读API文档注释String类创建字符串拼接字符串格式化字符串String方法substring(…

Vue.observable详解(细到原码)

文章目录 一、Observable 是什么二、使用场景三、原理分析参考文献 一、Observable 是什么 Observable 翻译过来我们可以理解成可观察的 我们先来看一下其在Vue中的定义 Vue.observable&#xff0c;让一个对象变成响应式数据。Vue 内部会用它来处理 data 函数返回的对象 返回…

项目进度管理

7过程 计划过程组6项&#xff1a;规划进度管理&#xff0c;定义活动&#xff0c;排列活动顺序&#xff0c;估算活动资源&#xff0c;估算活动持续时间&#xff0c;制定进度计划&#xff0c; 监控过程组1项&#xff1a;控制进度 1、规划进度管理&#xff0c; 对项目过程中管理…

定时任务-理论基础

什么是小顶堆 小顶堆&#xff08;Min Heap&#xff09;是一种特殊的二叉堆&#xff0c;它满足以下条件&#xff1a; 它是一个完全二叉树&#xff0c;即除了最后一层外&#xff0c;其他层的节点数都是满的&#xff0c;并且最后一层的节点从左到右依次排列。树中的每个节点的…

物联网智能控制器—福建蜂窝物联网科技有限公司

什么是物联网智能控制器&#xff1f; 物联网智能控制器是蜂窝物联自主研发的一种远程测控设备(RTU)&#xff0c;负责对现场信号、工业设备的监测和控制。本质上是一个模块化封装的微型计算机设备&#xff0c;将相应的一些功能进行了封装&#xff0c;无需进行电路设计和硬件程序…

Java多线程并发篇----第十二篇

系列文章目录 文章目录 系列文章目录前言一、ReentrantLock二、Condition 类和 Object 类锁方法区别区别三、tryLock 和 lock 和 lockInterruptibly 的区别前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章…

Python 网络爬虫入门详解

什么是网络爬虫 网络爬虫又称网络蜘蛛,是指按照某种规则在网络上爬取所需内容的脚本程序。众所周知,每个网页通常包含其他网页的入口,网络爬虫则通过一个网址依次进入其他网址获取所需内容。 优先申明:我们使用的python编译环境为PyCharm 一、首先一个网络爬虫的组成结构…

大括号内两行公式中,如何左对齐公式的条件

1. 先建立一个大括号&#xff0c;中间设置一个二维矩阵如下&#xff1a; 2. 选中整个矩阵&#xff0c;不要选外面的括号&#xff0c;进行如下操作 3. 选择左侧对齐 即可。

wechatpay-java 部署linux报错

ruoyimall部署linux环境报错 报错现象 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name wechatPayService: Unsatisfied dependency expressed through field service; nested exception is org.springframework.beans.fa…

1024 科学计数法 (20)

科学计数法是科学家用来表示很大或很小的数字的一种方便的方法&#xff0c;其满足正则表达式 [-][1-9].[0-9]E[-][0-9]&#xff0c;即数字的整数部分只有 1 位&#xff0c;小数部分至少有 1 位&#xff0c;该数字及其指数部分的正负号即使对正数也必定明确给出。 现以科学计数…

南京观海微电子----时序图绘制工具

Wavedrom 是一款功能强大且简单易用的文本转图表工具&#xff0c;被广泛应用于生成时序图、波形图等交互式波形。其特点在于使用简单的文本语法&#xff0c;使得开发人员能够以可视化的方式表示数字信号和时间序列数据。Wavedrom 的优势在于其高度灵活性和可扩展性&#xff0c;…

【数据库】聊聊MySQL事务隔离级别与锁机制

概述 针对事务来说&#xff0c;其实主要解决的就是数据的一致性&#xff0c;对于任何的存储中间件来说&#xff0c;都会存在并发访问数据的问题&#xff0c;编程语言层面 juc、go等机制 使用编程上的方式&#xff0c;加锁、无锁编程等。而数据库也存在多个连接访问修改同一个数…

远程开发之端口转发

远程开发之端口转发 涉及的软件forwarded port 通过端口转发&#xff0c;实现在本地电脑上访问远程服务器上的内网的服务。 涉及的软件 vscode、ssh forwarded port 在ports界面中的port字段&#xff0c;填需要转发的IP:PORT&#xff0c;即可转发远程服务器中的内网端口到本…

将图片添加到 PDF 的 5 种方法

需要一种称为 PDF 编辑器的特定工具才能将图片添加到 PDF。尽管大多数浏览器在查看和注释 PDF 文件方面都非常出色&#xff0c;但如果您使用图像到 PDF 技术&#xff0c;则只能将照片放入 PDF 中。无需修改即可将 PDF 文件恢复为原始格式的能力是使用此类软件程序甚至在线服务的…

[开发语言][c++]:Static关键字和全局变量

Static关键字和全局变量 1. 生命周期、作用域和初始化时机2. 全局变量3. Static 关键字3.1 面向过程3.1.1 静态全局变量3.1.2 静态局部变量&#xff08;单例中会使用&#xff09;3.1.3 静态函数 3.2 面向对象3.2.1 类内静态成员变量3.2.2 类内静态成员函数 Reference 写在前面&…