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为 OR
                                  portMAX_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为 OR
                                  portMAX_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.mfbz.cn/a/319021.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【漏洞复现】优卡特脸爱云一脸通智慧管理平台权限绕过漏洞CVE-2023-6099(1day)

漏洞描述 脸爱云一脸通智慧管理平台1.0.55.0.0.1及其以下版本SystemMng.ashx接口处存在权限绕过漏洞,通过输入00操纵参数operatorRole,导致特权管理不当,未经身份认证的攻击者可以通过此漏洞创建超级管理员账户。 免责声明 技术文章仅供参考,任何个人和组织使用网络应当…

HTTP协议请求详解

✏️✏️✏️今天给大家分享的是 HTTP 请求部分的基础知识。 清风的CSDN博客 &#x1f6e9;️&#x1f6e9;️&#x1f6e9;️希望我的文章能对你有所帮助&#xff0c;有不足的地方还请各位看官多多指教&#xff0c;大家一起学习交流&#xff01; ✈️✈️✈️动动你们发财的小…

【教程】华为数据恢复的5个简单方法

您刚刚不小心从华为手机中删除了一些重要文件&#xff0c;现在您迫切希望将它们找回来。如果是这样&#xff0c;那么您现在可能会感到沮丧和无助。您可能已向您的朋友寻求帮助或在互联网上搜索答案&#xff0c;但似乎无济于事。 华为数据恢复的5个简单方法 好吧&#xff0c;别…

windows下如何搭建Yapi环境

今天使用YApi时发现原网址无法访问。这下只能本地部署了&#xff08;官方文档&#xff09;。 第一步&#xff1a;安装node.js 获取资源 nodejs: https://nodejs.org/en/downloadLinux安装yum install -y nodejs查看node版本node -v查看npm版本npm -v第二步&#xff1a;安装mo…

Python 网络爬虫入门详解

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

【CAN】Basic CAN和Full CAN

文章目录 1 Basic CAN和Full CAN区别2 Basic CAN和Full CAN使用场景 >>返回总目录<< 1 Basic CAN和Full CAN区别 Basic CAN和Full CAN的定义在AUTOSAR_SWS_CANDriver中的描述如下&#xff0c;Basic表示一个HardwareObject可以处理多个L-PDUs&#xff0c;Full表示…

C#,入门教程(17)——条件语句(if-else)的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(16)——可变数据类型&#xff08;var&#xff09;的基础知识与使用禁忌https://blog.csdn.net/beijinghorn/article/details/124032216 程序的核心是逻辑。 逻辑的核心是布尔条件表达式。 逻辑的主要体现形式之一是 if-else 语句…

探寻编程深渊:那些你无法想象的‘最差程序员’

在IT行业&#xff0c;有一类人让其他程序员闻风丧胆&#xff1a;那些最差的程序员。 他们的代码可能是漏洞百出&#xff0c;效率低下&#xff0c;甚至难以运行。他们可能对基本的编程概念一无所知&#xff0c;却自认为是个编程天才。那么&#xff0c;这些最差的程序员到底是什…

F-score 和 Dice Loss 原理及代码

文章目录 1. F-score1. 1 原理1. 2 代码2. Dice Loss2.1 原理2.2 代码 通过看开源图像语义分割库的源码&#xff0c;发现它对 Dice Loss 的实现方式&#xff0c;是直接调用 F-score 函数&#xff0c;换言之&#xff0c;Dice Loss 是 F-score的特殊情况。于是就研究了一下这背后…

python实现网络爬虫代码_python如何实现网络爬虫

python实现网络爬虫的方法&#xff1a;1、使用request库中的get方法&#xff0c;请求url的网页内容&#xff1b;2、【find()】和【find_all()】方法可以遍历这个html文件&#xff0c;提取指定信息。 python实现网络爬虫的方法&#xff1a; 第一步&#xff1a;爬取 使用reque…

定时任务-理论基础

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

若依基于jsencrypt实现前后端登录密码加密

若依虽然有加密解密功能&#xff0c;然后只有前端有&#xff0c;在用户点击保存密码的时候&#xff0c;会将密码保存到本地&#xff0c;但是为了防止密码泄露&#xff0c;所以在保存的时候&#xff0c;进行加密&#xff0c;在回显密码的时候进行解密显示&#xff0c;用户在登录…

SpringCloud:Ribbon

文章目录 Ribbon快速入门Ribbon负载均衡算法常见的负载均衡算法更改算法规则修改配置 饥饿加载 Ribbon ribbon是一个客户端负载均衡器&#xff0c;会从注册中心拉取可用服务&#xff0c;当客户端需要获取服务请求时&#xff0c;ribbon能够解析服务地址并实现负载均衡 快速入门 …

Quick taxi route assignment via real-time intersection state prediction

Quick taxi route assignment via real-time intersection state prediction with a spatial-temporal graph neural network(通过时空图神经网络实时交叉口状态预测快速分配出租车路线) PAPER LINK 简单说一下: 本文采用了一种新的方法,通过使用空间-时间图神经网络(ST…

LMDeploy 的量化和部署

LMDeploy 的量化和部署 文档&#xff1a;https://github.com/InternLM/tutorial/blob/vansin-patch-4/lmdeploy/lmdeploy.md 视频&#xff1a;https://www.bilibili.com/video/BV1iW4y1A77P 一、模型量化 大模型参数量很大&#xff0c;运行起来非常消耗显存和内存&#xff0c;…

如何在电脑上免费更改 PDF 格式文档的字体大小?

对于需要编辑或修改的 PDF 文件来说&#xff0c;更改其字体大小是一个非常常见且必要的工作。虽然 Adobe Acrobat Pro DC 等专业的 PDF 编辑软件可以帮助您完成此任务&#xff0c;但他们通常都需要昂贵的恢复。幸运的是&#xff0c;有许多免费的 PDF 编辑工具可供选择。在本文中…

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

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

Docker安装Redis详细步骤

1、创建安装目录 mkdir -p /usr/local/docker/redis-docker 2、确定安装的版本 确定对应的版本&#xff0c;在步骤3中会用到&#xff1a; https://github.com/redis/redis/branches 3、配置docker-compose.yml 内容如下&#xff1a; version: 3 services:redis:image: r…

信息检索速通知识点

仅仅是我自己能想到的对这个分类的一个记忆。欢迎指正 首先&#xff0c;最重要的一点&#xff0c;什么是信息检索&#xff1f; 信息检索是从大规模无规则的数据中&#xff08;主要是文档&#xff09;中查询用户所需要的信息的过程。 然后&#xff0c;信息检索有哪几种索引呢&am…

Vue.observable详解(细到原码)

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