1、准备材料

正点原子stm32f407探索者开发板V2.4

STM32CubeMX软件(Version 6.10.0

Keil µVision5 IDE(MDK-Arm

野火DAP仿真器

XCOM V2.6串口助手

2、学习目标

本文主要学习 FreeRTOS 互斥量的相关知识,包括优先级翻转问题、优先级继承、死锁现象、创建/删除互斥量 和 获取/释放互斥量等知识

3、前提知识

3.1、优先级翻转问题

使用二值信号量用于进程间同步时可能会出现优先级翻转的问题,什么是“优先级翻转”问题呢?考虑如下所述的任务运行过程

  • 在 t1 时刻,低优先级的任务 TaskLP 切入运行状态,并且获取到了一个二值信号量 Binary Semaphores
  • 在 t2 时刻,高优先级的任务 TaskHP 请求获取二值信号量 Binary Semaphores ,但是由于 TaskLP 还未释放该二值信号量,所以在 t3 时刻,任务 TaskHP 进入阻塞状态等待二值信号量被释放
  • 在 t4 时刻,中等优先级的任务 TaskMP 进入就绪状态,由于不需要获取二值信号量,因此抢占低优先级任务任务 TaskLP 切入运行状态
  • 在 t5 时刻,任务 TaskMP 运行结束,任务 TaskLP 再次切入运行状态
  • 在 t6 时刻,任务 TaskLP 运行结束,释放二值信号量 Binary Semaphores,此时任务 TaskHP 从等待二值信号量的阻塞状态切入运行状态
  • 在t7时刻,任务 TaskHP 运行结束

根据上述流程读者可以发现一个问题,即在 t4 时刻中等优先级的任务 TaskMP 先于高优先级的任务 TaskHP 抢占了处理器,这破坏了 FreeRTOS 基于优先级抢占式执行的原则,我们将这种情况称为优先级翻转问题,上述描述的任务运行过程具体时刻流程图如下图所示

优先级翻转可能是一个严重的问题,但在小型嵌入式系统中,通常可以在系统设计时通过考虑如何访问资源来避免该问题

3.2、优先级继承

为了解决使用二值信号量可能会出现的优先级翻转问题,对二值信号量做了改进,增加了一种名为 “优先级继承” 的机制,改进后的实例称为了互斥量,注意虽然互斥量可以减缓优先级翻转问题的出现,但是并不能完全杜绝

接下来我们来通过例子介绍什么是优先级继承?

仍然考虑由 “3.1、优先级翻转问题” 小节中提出的任务运行过程的例子,具体流程如下所述,读者可以细心理解其中的不同之处

  • 在 t1 时刻,低优先级的任务 TaskLP 切入运行状态,并且获取到了一个互斥量 Mutexes
  • 在 t2 时刻,高优先级的任务 TaskHP 请求获取互斥量 Mutexes ,但是由于 TaskLP 还未释放该互斥量,所以在 t3 时刻,任务 TaskHP 进入阻塞状态等待互斥量被释放,但是与二值信号量不同的是,此时 FreeRTOS 将任务 TaskLP 的优先级临时提高到与任务 TaskHP 一致的优先级,也即高优先级
  • 在 t4 时刻,中等优先级的任务 TaskMP 进入就绪状态发生任务调度,但是由于任务 TaskLP 此时优先级被提高到了高优先级,因此任务 TaskMP 仍然保持就绪状态等待优先级较高的任务执行完毕
  • 在 t5 时刻,任务 TaskLP 执行完毕释放互斥量 Mutexes,此时任务 TaskHP 抢占处理器切入运行状态,并恢复任务 TaskLP 原来的优先级
  • 在 t6 时刻,任务 TaskHP 执行完毕,此时轮到任务 TaskMP 执行
  • 在 t7 时刻,任务 TaskMP 运行结束

根据互斥量的上述任务流程读者可以发现与二值信号量的不同之处,上述描述的任务运行过程具体时刻流程图如下图所示

3.3、什么是互斥量?

互斥量/互斥锁是一种特殊类型的二进制信号量,用于控制对在两个或多个任务之间共享资源的访问

互斥锁可以被视为一个与正在共享的资源相关联的令牌,对于合法访问资源的任务,它必须首先成功 “获取” 令牌,成为资源的持有者,当持有者完成对资源的访问之后,其需要 ”归还” 令牌,只有 “归还” 令牌之后,该令牌才可以再次被其他任务所 “获取” ,这样保证了互斥的对共享资源的访问,上述机制如下图所示 (注释1)

3.4、死锁现象

“死锁” 是使用互斥锁进行互斥的另一个潜在陷阱,当两个任务因为都在等待对方占用的资源而无法继续进行时,就会发生死锁,考虑如下所述的情况

  1. 任务 A 执行并成功获取互斥量 X
  2. 任务 A 被任务 B 抢占
  3. 任务 B 在尝试获取互斥量 X 之前成功获取互斥量 Y,但互斥量 X 由任务 A 持有,因此对任务 B 不可用,任务 B 选择进入阻塞状态等待互斥量 X 被释放
  4. 任务 A 继续执行,它尝试获取互斥量 Y,但互斥量 Y 由任务 B 持有,所以对于任务 A 来说是不可用的,任务 A 选择进入阻塞状态等待待释放的互斥量 Y

经过上述的这样一个过程,读者可以发现任务 A 在等待任务 B 释放互斥量 Y ,而任务 B 在等待任务 A 释放互斥量 X ,两个任务都在阻塞状态无法执行,从而导致 ”死锁“ 现象的发生,与优先级翻转一样,避免 “死锁” 的最佳方法是在设计时考虑其潜在影响,并设计系统以确保不会发生死锁

3.5、什么是递归互斥量?

任务也有可能与自身发生死锁,如果任务尝试多次获取相同的互斥体而不首先返回互斥体,就会发生这种情况,考虑以下设想:

  1. 任务成功获取互斥锁
  2. 在持有互斥体的同时,任务调用库函数
  3. 库函数的实现尝试获取相同的互斥锁,并进入阻塞状态等待互斥锁变得可用

在此场景结束时,任务处于阻塞状态以等待互斥体返回,但任务已经是互斥体持有者。 由于任务处于阻塞状态等待自身,因此发生了死锁

通过使用递归互斥体代替标准互斥体可以避免这种类型的死锁,同一任务可以多次 “获取” 递归互斥锁,并且只有在每次 “获取” 递归互斥锁之后都调用一次 “释放” 递归互斥锁,才会返回该互斥锁

因此递归互斥量可以视为特殊的互斥量,一个互斥量被一个任务获取之后就不能再次获取,其他任务想要获取该互斥量必须等待这个任务释放该互斥连,但是递归互斥量可以被一个任务重复获取多次,当然每次获取必须与一次释放配对使用

注意不管是互斥量,还是递归互斥量均存在优先级继承机制,但是由于 ISR 并不是任务,因此互斥量和递归互斥量不能在中断中使用

3.5、创建互斥量

互斥量在使用之前必须先创建,因为互斥量分为互斥量和递归互斥量两种,所以 FreeRTOS 也提供了不同的 API 函数,具体如下所述

/**
* @brief 动态分配内存创建互斥信号量函数
* @retval 创建互斥信号量的句柄
*/
SemaphoreHandle_t xSemaphoreCreateMutex(void); /**
* @brief 静态分配内存创建互斥信号量函数
* @param pxMutexBuffer:指向StaticSemaphore_t类型的变量,该变量将用于保存互斥锁型信号量的状态
* @retval 返回成功创建后的互斥锁的句柄,如果返回NULL则表示内存不足创建失败
*/
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxMutexBuffer); /**
* @brief 动态分配内存创建递归互斥信号量函数
* @retval 创建递归互斥信号量的句柄,如果返回NULL则表示内存不足创建失败
*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void); /**
* @brief 动态分配内存创建二值信号量函数
* @param pxMutexBuffer:指向StaticSemaphore_t类型的变量,该变量将用于保存互斥锁型信号量的状态
*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(
StaticSemaphore_t pxMutexBuffer);

3.6、获取互斥量

获取互斥量直接使用获取信号量的函数即可,但对于递归互斥量需要专门的获取函数,具体如下所述

/**
* @brief 获取信号量函数
* @param xSemaphore:正在获取的信号量的句柄
* @param xTicksToWait:等待信号量变为可用的时间
* @retval 成功获取信号量则返回pdTRUE, xTicksToWait过期,信号量不可用,则返回pdFALSE
*/
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait); /**
* @brief 获取递归互斥量
* @param xMutex:正在获得的互斥锁的句柄
* @param xTicksToWait:等待信号量变为可用的时间
* @retval 成功获取信号量则返回pdTRUE, xTicksToWait过期,信号量不可用,则返回pdFALSE
*/
BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xMutex,
TickType_t xTicksToWait);

3.7、释放互斥量

释放互斥量直接使用释放信号量的函数即可,但对于递归互斥量需要专门的释放函数,具体如下所述

/**
* @brief 释放信号量函数
* @param xSemaphore:要释放的信号量的句柄
* @retval 成功释放信号量则返回pdTRUE, 若发生错误,则返回pdFALSE
*/
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore); /**
* @brief 释放递归互斥量
* @param xMutex:正在释放或“给出”的互斥锁的句柄
* @retval 成功释放递归互斥量后返回pdTRUE
*/
BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t xMutex);

3.8、删除互斥量

直接使用信号量的删除函数即可,具体如下所述

/**
* @brief 获取信号量函数
* @param xSemaphore:要删除的信号量的句柄
* @retval None
*/
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore);

4、实验一:优先级翻转问题

4.1、实验目标

既然实验是讨论优先级翻转问题,那么我们来复现 “3.1、优先级翻转问题” 小节中所描述到的任务运行过程,具体如下所述

  1. 创建一个二值信号量 BinarySem_PI,用于演示优先级翻转问题
  2. 创建一个低优先级任务 Task_Low ,在该任务中获取二值信号量 BinarySem_PI ,并通过延时模拟长时间连续运行,运行结束后释放该二值信号量,整个过程会通过串口输出提示信息
  3. 创建一个中等优先级任务 Task_Middle,该任务负责在 Task_Low 模拟长时间连续运行期间抢占其处理器控制权限
  4. 创建一个高优先级任务 Task_High,该任务总是尝试获取二值信号量 BinarySem_PI

4.2、CubeMX相关配置

首先读者应按照"FreeRTOS教程1 基础知识"章节配置一个可以正常编译通过的 FreeRTOS 空工程,然后在此空工程的基础上增加本实验所提出的要求

本实验需要初始化 USART1 作为输出信息渠道,具体配置步骤请阅读“STM32CubeMX教程9 USART/UART 异步通信”,如下图所示

单击 Middleware and Software Packs/FREERTOS,在 Configuration 中单击 Tasks and Queues 选项卡双击默认任务修改其参数,然后增加另外两个不同优先级的任务,具体如下图所示

然后在 Configuration 中单击 Timers and Semaphores ,在 Binary Semaphores 中单击 Add 按钮新增加一个名为 BinarySem_PI 的二值信号量,具体如下图所示

配置 Clock Configuration 和 Project Manager 两个页面,接下来直接单击 GENERATE CODE 按钮生成工程代码即可

4.3、添加其他必要代码

按照 “STM32CubeMX教程9 USART/UART 异步通信” 实验 “6、串口printf重定向” 小节增加串口 printf 重定向代码,具体不再赘述

首先应该在 freertos.c 中添加信号量相关 API 和 printf() 函数的头文件,如下所述

/*freertos.c中添加头文件*/
#include "semphr.h"
#include "stdio.h"

然后在该文件中实现三个不同优先级的任务,主要是一些串口输出给用户的提示信息,方便演示实验目的,具体如下所述

/*低优先级任务*/
void AppTask_Low(void *argument)
{
/* USER CODE BEGIN AppTask_Low */
/* Infinite loop */
uint8_t str1[]="Task_Low take it\r\n";
uint8_t str2[]="Task_Low give it\r\n";
uint8_t str3[]="return Task_Low\r\n";
for(;;)
{
//获取信号量
if(xSemaphoreTake(BinarySem_PIHandle, pdMS_TO_TICKS(200))==pdTRUE)
{
printf("%s",str1);
//模拟任务连续运行
HAL_Delay(500);
printf("%s",str3);
HAL_Delay(500);
printf("%s",str2);
//释放信号量
xSemaphoreGive(BinarySem_PIHandle);
}
}
/* USER CODE END AppTask_Low */
} /*中等优先级任务*/
void AppTask_Middle(void *argument)
{
/* USER CODE BEGIN AppTask_Middle */
/* Infinite loop */
uint8_t strMid[]="Task_Middle is running\r\n";
for(;;)
{
printf("%s", strMid);
vTaskDelay(500);
}
/* USER CODE END AppTask_Middle */
} /*高优先级任务*/
void AppTask_High(void *argument)
{
/* USER CODE BEGIN AppTask_High */
/* Infinite loop */
uint8_t strHigh1[]="Into Task_High\r\n";
uint8_t strHigh2[]="Task_High get token\r\n";
uint8_t strHigh3[]="Task_High give token\r\n";
for(;;)
{
printf("%s",strHigh1);
//获取信号量
if(xSemaphoreTake(BinarySem_PIHandle, portMAX_DELAY)==pdTRUE)
{
printf("%s",strHigh2);
printf("%s",strHigh3);
//释放信号量
xSemaphoreGive(BinarySem_PIHandle);
}
vTaskDelay(500);
}
/* USER CODE END AppTask_High */
}

在 "FreeRTOS教程5 信号量" 文章 ”3.2、创建信号量“ 小节中曾提到,信号量被创建完之后是无效的,但是这里我们需要让刚创建的二值信号量有效,否则 Task_High 和 Task_Low 都将无法获取二值信号量,因此最后修改二值信号量的初始值为 1 即可,具体如下所示

/*将初始值0修改为1*/
BinarySem_PIHandle = osSemaphoreNew(1, 1, &BinarySem_PI_attributes);

4.4、烧录验证

烧录程序,打开串口助手,按住开发板复位按键,目的是为了让串口助手接收程序从最开始输出的信息,这里我们只分析第一轮,因为延时、语句执行等微小的时间差异会导致第二轮任务进入阻塞和退出阻塞的时间与第一轮有差异,如下所述为第一轮详细的任务执行流程

  1. 当创建完三个不同优先级的任务后不会立即得到执行,而是进入就绪状态等待调度器的启动
  2. 当调度器启动之后会按照优先级从最高优先级开始执行,因此串口输出 “Into Task_High” 表示进入高优先级任务,然后在高优先级任务 Task_High 中获得二值信号量,然后立马释放二值信号量,最后进入 500ms 的阻塞状态
  3. 当高优先级任务进入阻塞状态后,接下来会执行就绪状态的中等优先级任务 Task_Middle ,该任务无具体功能,仅仅通过串口输出 “Task_Middle is running”,然后同样进入 500ms 的阻塞状态
  4. 由于高优先级和中等优先级任务都进入阻塞状态,这时才轮到低优先级任务 Task_Low 执行,低优先级任务 Task_Low 成功获取到二值信号量并通过串口输出 “Task_Low take it” ,然后利用 500ms 的 HAL 库延时函数模拟连续运行
  5. 在 Task_Low 连续运行期间,在其即将执行完第一个 HAL_Delay(500); 时,高优先级任务 Task_High 从 500ms 的阻塞状态恢复,然后尝试获取已经被 Task_Low 获取的二值信号量,结果就是进入阻塞状态等待 Task_Low 释放二值信号量
  6. 紧接着 Task_Middle 从 500ms 的阻塞状态恢复,通过串口输出 “Task_Middle is running”,接着再次进入 500ms 阻塞状态
  7. 由于高优先级和中等优先级任务再次进入阻塞状态,因此调度器返回 Task_Low 被抢占时的程序处继续执行,因此 Task_Low 通过串口输出 “return Task_Low” ,然后利用第二个 HAL_Delay(500); 继续模拟长时间运行
  8. 在 Task_Low 第二个 HAL_Delay(500); 即将执行完毕时,Task_Middle 再次从 500ms 的阻塞状态恢复,通过串口输出 “Task_Middle is running” ,然后再次进入 500ms 阻塞状态(这里 Task_High 由于不是因为延时进入的阻塞状态所以未恢复运行状态)
  9. 最后返回 Task_Low 任务,释放二值信号量,一旦 Task_Low 任务释放二值信号量,等待二值信号量的高优先级任务 Task_High 会立马退出阻塞状态成功获取到二值信号量,并会通过串口输出 “Task_High get token“

从上述过程可知,从 Task_Low 获取二值信号量之后到第一轮结束,Task_High 等待 Task_Low 释放二值信号量,等待期间中等优先级的任务 Task_Middle 却先于高优先级任务 Task_High 得到了执行,这就是所谓的优先级翻转问题,上述过程所述的实际串口输出如下图所示

4.5、互斥量的应用

首先在 STM32CubeMX 中单击 Middleware and Software Packs/FREERTOS,在 Configuration 中单击 Mutexes 选项卡,单击 Add 按钮增加互斥量 Mutex_PI ,具体如下图所示

然后将上述实验使用的所有二值信号量句柄 BinarySem_PIHandle 修改为互斥量 Mutex_PIHandle,不需要做其他任何操作,烧录程序即可

打开串口助手,观察串口助手的输出,如下所述为第一轮详细的任务执行流程

  1. 前4个步骤与 ”4.4、烧录验证“ 小节一致,只不过从二值信号量修改为互斥量
  2. 在第5步时,高优先级任务 Task_High 从 500ms 的阻塞状态恢复,输出 ”Into Task_High“ ,然后尝试获取已经被 Task_Low 获取的互斥量,结果就是进入阻塞状态等待 Task_Low 释放互斥量,同时将 Task_Low 的优先级临时提高到和高优先级任务 Task_High 一样的优先级
  3. 紧接着 Task_Middle 从 500ms 的阻塞状态恢复,但是由于现在 Task_Low 任务的优先级要高于中等优先级任务 Task_Middle ,因此不能抢占 Task_Low 任务,故无法执行任务体输出 ”Task_Middle is running“ ,所以其状态变为就绪状态,它将等待所有高优先级的任务执行完后才会执行
  4. 于是优先级被临时提高到高优先级的任务 Task_Low 继续执行其函数体内容,输出 ”return Task_Low“ ,然后执行第二个 HAL_Delay(500); ,最后释放互斥量,通过串口输出 ”Task_Low give it“
  5. 一旦互斥量被 Task_Low 释放,处于阻塞状态的 Task_High 就会立马恢复运行状态获取到互斥量,所以会通过串口输出 ”Task_High get token“ 和 ”Task_High give token“ ,同时当互斥量被 Task_High 任务成功获取之后,会将任务 Task_Low 临时提高的优先级恢复到其原来的低优先级,最后 Task_High 调用延时函数进入 500ms 的阻塞状态
  6. 当高优先级任务 Task_High 进入阻塞状态后,系统内现在剩余就绪状态的中等优先级任务 Task_Middle 和 低优先级任务 Task_Low ,所以轮到 Task_Middle 任务执行,其将通过串口输出 ”Task_Middle is runing“ ,至此一轮结束

读者可以自行对比将二值信号量更换为互斥量之后的串口输出结果,可以发现在步骤4中,中等优先级的任务 Task_Middle 不再先于高优先级的任务 Task_High 得到执行,上述整个过程串口数据的完整输出如下图所示

5、注释详解

注释1:图片来源于 Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf

参考资料

STM32Cube高效开发教程(基础篇)

Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf

FreeRTOS教程6 互斥量的更多相关文章

  1. 【freertos】011-信号量、互斥量及优先级继承机制源码分析

    目录 前言 11.1 任务同步 11.2 信号量概念 11.3 二值信号量 11.3.1 二值信号量概念 11.3.2 优先级翻转 11.3.3 二值信号量运作机制 11.4 计数信号量 11.4.1 ...

  2. FreeRTOS的信号量和互斥量

    1. 理解如下,言简意赅的说,信号量解决同步,互斥量解决竞争. 信号量用于同步,主要任务间和中断间同步:互斥量用于互锁,用于保护同时只能有一个任务访问的资源,为资源上一把锁. 互斥量具有优先级继承,信 ...

  3. 多线程相关------互斥量Mutex

    互斥量(Mutex) 互斥量是一个可以处于两态之一的变量:解锁和加锁.只有拥有互斥对象的线程才具有访问资源的权限.并且互斥量可以用于不同进程中的线程的互斥访问. 相关函数: CreateMutex用于 ...

  4. Linux/Unix 线程同步技术之互斥量(1)

    众所周知,互斥量(mutex)是同步线程对共享资源访问的技术,用来防止下面这种情况:线程A试图访问某个共享资源时,线程B正在对其进行修改,从而造成资源状态不一致.与之相关的一个术语临界区(critic ...

  5. 【转】【Linux】 临界区,互斥量,信号量,事件的区别

    原文地址:http://blog.itpub.net/10697500/viewspace-612045/ Linux中 四种进程或线程同步互斥的控制方法1.临界区:通过对多线程的串行化来访问公共资源 ...

  6. Linux 多线程互斥量互斥

    同步 同一个进程中的多个线程共享所在进程的内存资源,当多个线程在同一时刻同时访问同一种共享资源时,需要相互协调,以避免出现数据的不一致和覆盖等问题,线程之间的协调和通信的就叫做线程的同步问题, 线程同 ...

  7. linux线程同步(1)-互斥量

    一.概述                                                   互斥量是线程同步的一种机制,用来保护多线程的共享资源.同一时刻,只允许一个线程对临界区进行 ...

  8. [转]一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程

    一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程 希望此文能给初学多线程编程的朋友带来帮助,也希望牛人多多指出错误. 另外感谢以下链接的作者给予,给我的学习带来了很大帮助 http ...

  9. Linux驱动学习笔记(6)信号量(semaphore)与互斥量(mutex)【转】

    转自:http://blog.chinaunix.net/uid-24943863-id-3193530.html 并发导致竟态,从而导致对共享数据的非控制访问,产生非预期结果,我们要避免竟态的发生. ...

  10. 经典线程同步 互斥量Mutex

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...

随机推荐

  1. Dubbo本地调试方法

    方法一:用版本号来区分 比如,开发环境上跑的服务版本是1.0.0,那么为了在本地打断点调试某个服务,可以在本地启动,将version设置为2.0.0 服务提供者 @DubboService(versi ...

  2. Context与Reducer

    Context与Reducer Context是React提供的一种跨组件的通信方案,useContext与useReducer是在React 16.8之后提供的Hooks API,我们可以通过use ...

  3. VueRouter导航守卫

    VueRouter导航守卫 vue-router提供的导航守卫主要用来通过跳转或取消的方式守卫导航,简单来说导航守卫就是路由跳转过程中的一些钩子函数,路由跳转是一个大的过程,这个大的过程分为跳转前中后 ...

  4. valueOf与toString

    valueOf与toString valueOf和toString是Object.prototype上的方法,在Js几乎所有的对象都会继承自Object,同样由于包装对象的原因,几乎所有的数据类型都能 ...

  5. 《系列二》-- 5、单例bean缓存的获取

    目录 1 判断bean是否完成整个加载流程 2 判断当前bean是否被加载过,是否已作为提前暴露的bean 关于循环依赖 阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内 ...

  6. 解密prompt系列24. RLHF新方案之训练策略:SLiC-HF & DPO & RRHF & RSO

    去年我们梳理过OpenAI,Anthropic和DeepMind出品的经典RLHF论文.今年我们会针对经典RLHF算法存在的不稳定,成本高,效率低等问题讨论一些新的方案.不熟悉RLHF的同学建议先看这 ...

  7. 关于谷歌浏览器出现“错误代码:net::ERR_UNSAFE_PORT”的解决办法

    搭建项目时需要自己配置端口信息,但是有人搭建之后会出现如下情况 但是换用edge等浏览器没有问题,这是因为chorme浏览器有自己的默认非安全端口, 若访问这些端口就会出现这个错误,并且所有采用cho ...

  8. 文心一言 VS 讯飞星火 VS chatgpt (202)-- 算法导论15.3 1题

    一.对于矩阵链乘法问题,下面两种确定最优代价的方法哪种更高效?第一种方法是穷举所有可能的括号化方案,对每种方案计算乘法运算次数,第二种方法是运行RECURSIVE-MATRIX-CHAIN.证明你的结 ...

  9. linux下docker安装与初始

    1 docker的安装与使用初识 1 docker的安装 # step 1: 安装必要的一些系统工具 sudo yum install -y yum-utils device-mapper-persi ...

  10. 【LeetCode二叉树#16】二叉(搜索)树的最近公共祖先(递归后序遍历,巩固回溯机制)

    二叉树的最近公共祖先 力扣题目链接(opens new window) 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先. 百度百科中最近公共祖先的定义为:"对于有根树 T 的两个结点 ...