直接带你使用 FreeRTOS 的 API 函数(基于 CubeMX 生成)(不断更新)

news2025/6/2 7:27:34

在这里插入图片描述

作者有话要说

对于这个越来约浮躁的社会,什么都要钱,特别是网上那些垃圾教程,越听越模糊,那行吧,我直接就从 FreeRTOS 的 API函数 学起,管你这么多底层内容的,以后再说吧!!!(对不起!还是讲了不少底层知识。。。)

我写的只是简略描述一下这些 API ,然后直接教你怎么调用,但是具体的 API 函数参考官网的 --> FreeRTOS API 引用

CubeMX 配置

这个是大佬写的配置,感觉可以的,就放在这里了,配置我就不写了

STM32CubeMX学习笔记(28)——FreeRTOS实时操作系统使用(任务管理)_thread id identifies the thread._Leung_ManWah的博客-CSDN博客

初期配置

虽然在 cubemx 配置的都是这个文件,但是可以打开了解一下,也可以直接跳过。

先找到 FreeRTOS.c 这个文件,在头位置找到 FreeRTOS.h 文件跳转过去,往下翻一翻就可以看到 调用了 FreeRTOSConfig.h 文件,可以过来看看,里面写了啥,至于为什么要看,那当然是,省的老是打开 CubeMX 配置生成来去,又卡又慢的。

找到配置文件

对于这部分内容想详细了解的可以看下官网(有中文还是挺香的)。

FreeRTOS - The Free RTOS configuration constants and configuration options - FREE Open Source RTOS for small real time embedded systems

注意事项

  1. 使用 STM32CubeMX 代码生成,在 STM32Cube 固件中,通过 ARM 提供的通用 CMSIS-OS( cmsis_os.h 和 cmsis_os.c 文件,这两个文件把给 FreeRTOS 封装了一层,调用这其中的各类函数,和直接调用 FreeRTOS 的函数没有区别 ) 封装层,将 FreeRTOS 用作实时操作系统。也就是说在一套代码里有着两套标准,在阅读源码时需要注意区分。我主要讲解的是 FreeRTOS 的函数,配合 CubeMX 使用,而尽量少用 CMSIS-OS 封装层。
  2. 等待补充。。。

任务创建

这一章的创建任务可以由 CubeMX 生成,任务删除可以看看。

任务创建由两种方法,一种是动态创建,一种是静态创建。

这一章就介绍上面两个 创建任务函数,加一个 删除任务的函数

动态创建

函数介绍

对于 动态创建 的 函数如下:

 BaseType_t xTaskCreate(    TaskFunction_t pvTaskCode,
                            const char * const pcName,
                            configSTACK_DEPTH_TYPE usStackDepth,
                            void *pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t *pxCreatedTask
                          );

参数分别是:

  1. 指向任务入口函数的指针(pvTaskCode):这里要传入的参数是一个自定义的函数,这个任务应该是要符合规则,就是符合 带有一个参数(这个参数是一个 void * 类型,就是可以传入任意类型参数) 的 无返回值 的函数。
    • 任务函数结构
  2. 任务名字(pcName)。
  3. 任务堆栈大小(usStackDepth):以 size_t(这里的 size_t 是 4 个字节(32位系统中的 int 的大小)) 为最小步进长度的堆栈大小。
    • 假如传入 128,则堆栈大小是 ( 128 * sizeof( size_t ) ) Byte。
    • 堆栈最小步进长度
  4. 任务的传入形参(pvParameters),在任务内部可以获取这个形参的值(貌似用的比较少)。
  5. 任务优先级(uxPriority),有这几个优先级可以选择:
    • 这里虽然写了 负的优先级,但是会在创建任务的时候改到 0 值以上( CMSIS-OS 封装层 改值后传入 FreeRTOS 的创建任务中)。任务优先级
    • 这里定义优先级的范围可以是 4 到 32,必须有至少 4 种优先级优先级选择
    • 不知到为什么,我在 cubemx 调成了32的优先级最大值,而在代码中还是只有这几种优先级可以选择,但是我翻看源码的时候,里面虽然支持 0 到 31的优先级,这里却只会枚举定义这么几个。。(现在知道了,是因为 cmsis_os .c/.h 文件是 ARM 提供的通用 CMSIS-OS 封装层,CubeMX 生成不改这个,改的是 FreeRTOSConfig.h 文件,也就是优先级支持了,可以直接传入数字进去,不用管那几个枚举值 )
    • 优先级支持范围
  6. 任务句柄(pxCreatedTask),类型是void * 类型,指针指向的是一个 tskTCB(任务控制块)(保存任务的数据结构体) 结构体,里面存储的是这个任务的

返回值是:用于判断创建是否成功的,当值为 pdPASS 时创建成功,当值为 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 时创建失败

函数使用

TaskHandle_t myTask01Handle;	// hal 库生产的是 osThreadId myTask01Handle; 两者没有区别 osThreadId 就是对 TaskHandle_t起了别名

// 任务调度函数
void StartTask01(void const * argument)
{
  for(;;)
  {
		printf("I'm Task01.\r\n");
		vTaskDelay(10);	// 自动进入阻塞状态,等待
  }
}

void main(void)
{
    // 动态的创建一个任务啦,参数可以看前面的解释
    if(xTaskCreate((TaskFunction_t)StartTask01, "myTask01", 128, NULL, (UBaseType_t)osPriorityIdle, myTask01Handle) == pdPASS)
    {
        // 创建成功打印一下啦
    	printf("Create myTask01 OK!\r\n");
    }
    
    vTaskStartScheduler();   /* 启动任务,开启调度 */
    /* 永远不应该到达这里,因为现在控制权已经被调度器接管了 */
    while(1);
}


  

静态创建

一般比较少用这个,不是重点。

函数介绍

 TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
                                 const char * const pcName,
                                 const uint32_t ulStackDepth,
                                 void * const pvParameters,
                                 UBaseType_t uxPriority,
                                 StackType_t * const puxStackBuffer,
                                 StaticTask_t * const pxTaskBuffer );

参数分别是:

  1. 任务函数起始地址(pxTaskCode),和动态创建描述一样。
  2. 任务名字(pcName)
  3. 任务堆栈大小(ulStackDepth),用于指定传入的 puxStackBuffer 参数的 buf 的大小,最小步进大小是 sizeof( StackType_t ) 。
    • 假如传入 128,则堆栈大小是 ( 128 * sizeof( StackType_t ) ) Byte,此时 puxStackBuffer 的数组必须有 128 的索引。
  4. 任务的传入形参(pvParameters),和动态创建描述一样。
  5. 任务优先级(uxPriority),和动态创建描述一样。
  6. 必须指向至少具有 ulStackDepth 索引的 StackType_t 数组(请参阅上面的 ulStackDepth 参数),该数组用作任务的堆栈,因此必须是永久性的(全局,或静态)。
  7. 是一个 StaticTask_t * 变量,在函数内部被强转成 TCB_t * 变量,然后获取各种 TCB_t(保存任务的数据结构体) 内容,最后给函数当返回值。

返回值是:返回一个TCB_t指针(这个tcb结构体保存的内容和 pxTaskBuffer 参数是相同的),若是为空,则创建失败。

函数使用

// 一定要放在全局的位置,使栈空间永久存在
StackType_t myTask02Buff[128];
StaticTask_t myTask02Handle;
// 任务调度函数
void StartTask02(void const * argument)
{
  for(;;)
  {
		printf("I'm Task02.\r\n");
		vTaskDelay(10);	// 自动进入阻塞状态,等待
  }
}

void main(void)
{
    // 静态的创建一个任务啦,参数可以看前面的解释
    if(xTaskCreateStatic((TaskFunction_t)StartTask02, "myTask02", 128, NULL, (UBaseType_t)osPriorityIdle, myTask02Buff, &myTask02Handle) != NULL)
    {
    	printf("Create StartTask02 OK!\r\n");
    }
    
    vTaskStartScheduler();   /* 启动任务,开启调度 */
    /* 永远不应该到达这里,因为现在控制权已经被调度器接管了 */
    while(1);
}

删除任务

被删除的任务将从所有的就绪、阻塞、挂起和事件的列表中移除。

空闲任务的责任是要将分配给已删除任务的内存释放掉。

注意:只有内核为任务分配的内存空间才会在任务被删除后自动回收,任务自己占用的内存或资源需要由应用程序自己显式地释放。

打开删除函数

函数介绍

void vTaskDelete( TaskHandle_t xTask );

参数:

  1. xTask ,待删除的任务的句柄。传递 NULL 将导致调用他的任务被删除。
    • 这里注意一下,若是传入的参数是 NULL ,则会删除调用他的任务,这个怎么实现的?
    • 因为在 tasks.c 中有一个全局 TCB 指针,会指向当前任务 TCB,
    • 当前任务的 TCB 指针
    • 在 vTaskDelete 函数中调用了 prvGetTCBFromHandle 宏来判断该传入TCB参数是否为空,若是为空,则获取当前正在执行的任务的 TCB ,否则返回传入的 TCB 指针。判空 TCB获取 TCB

该函数没有返回值。

函数使用

// 一定要放在全局的位置,使栈空间永久存在

TaskHandle_t myTask01Handle;	// hal 库生产的是 osThreadId myTask01Handle; 两者没有区别 osThreadId 就是对 TaskHandle_t起了别名

StackType_t myTask02Buff[128];
StaticTask_t myTask02Handle;

// 任务调度函数
void StartTask01(void const * argument)
{
  for(;;)
  {
		printf("I'm Task01.\r\n");
		vTaskDelay(10);	// 自动进入阻塞状态,等待
  }
}



// 任务调度函数
void StartTask02(void const * argument)
{
    uint8_t i = 0;
  for(;;)
  {
		printf("I'm Task02.\r\n");
		vTaskDelay(10);	// 自动进入阻塞状态,等待
      	// 当 i > 20 时,释放当前线程 ( StartTask02 )
		if(i++ > 20)
        {
        	vTaskDelete(NULL);
        }
  }
}

void main(void)
{
    // 动态的创建一个任务啦,参数可以看前面的解释
    if(xTaskCreate((TaskFunction_t)StartTask01, "myTask01", 128, NULL, (UBaseType_t)osPriorityIdle, myTask01Handle) == pdPASS)
    {
        // 创建成功打印一下啦
    	printf("Create myTask01 OK!\r\n");
    }
    // 静态的创建一个任务啦,参数可以看前面的解释
    if(xTaskCreateStatic((TaskFunction_t)StartTask02, "myTask02", 128, NULL, (UBaseType_t)osPriorityIdle, myTask02Buff, &myTask02Handle) != NULL)
    {
    	printf("Create StartTask02 OK!\r\n");
    }
    
    vTaskStartScheduler();   /* 启动任务,开启调度 */
    /* 永远不应该到达这里,因为现在控制权已经被调度器接管了 */
    while(1);
}

对于任务创建的扩展知识

对于CubeMX生成的任务创建函数解读

CubeMX 创建任务解读1

CubeMX 创建任务解读2

任务的创建包含两个部分,第一部分是一个宏函数( 动态时为 osThreadDef )(静态时为 osThreadStaticDef),用于打包各个参数,给到一个新创建的 osThreadDef_t 类型变量,该变量名为,os_thread_def_##name,(在宏中,## 号用于连接两边。假如名字是123,则变量名为,os_thread_def_123)。

osThreadDef 和 osThreadStaticDef 宏
os_thread_def 结构体

对于任务创建的第二部分是一个函数(osThreadCreate),在函数内部直接调用本章的任务创建函数,来创建任务。第一个参数是,第一部分的宏创建的 osThreadDef_t 类型变量;第二个参数是,创建函数的第 4 个参数(pvParameters,任务的传入参数)。

osThreadCreate函数

TCB结构体是什么?

这一小节参考: FreeRTOS解析:TCB_t结构体及重要变量说明(Task-1)_stacktype结构体_Nrush的博客-CSDN博客

TCB_t 的全称为 Task Control Block,也就是任务控制块,这个结构体包含了一个任务所有的信息,它的定义以及相关变量的解释如下

/*
 * 任务控制块。为每个任务分配一个任务控制块(TCB),
 * 并存储任务状态信息,包括指向任务上下文(任务的运行时环境,包括寄存器值)的指针
 */
typedef struct tskTaskControlBlock
{
    // 这里栈顶指针必须位于TCB第一项是为了便于上下文切换操作,详见 xPortPendSVHandler 中任务切换的操作。
	volatile StackType_t	*pxTopOfStack;

	#if ( portUSING_MPU_WRAPPERS == 1 )
    	// MPU设置被定义为端口层的一部分。这必须是TCB结构的第二个成员。
		xMPU_SETTINGS	xMPUSettings;
	#endif

    // 表示任务状态,不同的状态会挂接在不同的状态链表下(就绪、阻塞、挂起)。
	ListItem_t			xStateListItem;
    // 事件链表项,会挂接到不同事件链表下
	ListItem_t			xEventListItem;
    // 任务优先级,数值越大优先级越高
	UBaseType_t			uxPriority;
    // 指向堆栈起始位置,这只是单纯的一个分配空间的地址,可以用来检测堆栈是否溢出
	StackType_t			*pxStack;
    // 任务名
	char				pcTaskName[ configMAX_TASK_NAME_LEN ];
	// 指向栈尾,可以用来检测堆栈是否溢出
	#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
		StackType_t		*pxEndOfStack;
	#endif
	// 记录临界段的嵌套层数
	#if ( portCRITICAL_NESTING_IN_TCB == 1 )
		UBaseType_t		uxCriticalNesting;
	#endif
	// 跟踪调试用的变量
	#if ( configUSE_TRACE_FACILITY == 1 )
		UBaseType_t		uxTCBNumber;
		UBaseType_t		uxTaskNumber;
	#endif
	// 任务优先级被临时提高时,保存任务原本的优先级
	#if ( configUSE_MUTEXES == 1 )
		UBaseType_t		uxBasePriority;
		UBaseType_t		uxMutexesHeld;
	#endif
	// 任务的一个标签值,可以由用户自定义它的意义,例如可以传入一个函数指针可以用来做 Hook 函数调用
	#if ( configUSE_APPLICATION_TASK_TAG == 1 )
		TaskHookFunction_t pxTaskTag;
	#endif
	// 任务的线程本地存储指针,可以理解为这个任务私有的存储空间
	#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
		void			*pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
	#endif
	// 运行时间变量
	#if( configGENERATE_RUN_TIME_STATS == 1 )
		uint32_t		ulRunTimeCounter;
	#endif
	// 支持NEWLIB的一个变量
	#if ( configUSE_NEWLIB_REENTRANT == 1 )
		struct	_reent xNewLib_reent;
	#endif
	// 任务通知功能需要用到的变量
	#if( configUSE_TASK_NOTIFICATIONS == 1 )
    	// 任务通知的值 
		volatile uint32_t ulNotifiedValue;
    	// 任务通知的状态
		volatile uint8_t ucNotifyState;
	#endif

	// 用来标记这个任务的栈是不是静态分配的
	#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
		uint8_t	ucStaticallyAllocated;
	#endif
	// 延时是否被打断
	#if( INCLUDE_xTaskAbortDelay == 1 )
		uint8_t ucDelayAborted;
	#endif

} tskTCB;

/* 起一个别名 */
typedef tskTCB TCB_t;

创建时的 TCB 获取

对于 任务相关的 API 由一个在 task. h 中 TaskHandle_t 实际就是一个 void * 类型用于接收所有类型的指针,在创建任务时不管调用 xTaskCreate 还是 xTaskCreateStatic 都会获得一个 TaskHandle_t 类型的值,我在追溯到最后时,看见实际这个 TaskHandle_t 指针获取的是一个 TCB_t 的结构体。

拿动态创建任务函数举例:

开始传入的一个 pxCreatedTask 一般是一个空指针,用于接收在函数内部 TCB指针,里面包含任务的信息。

任务创建时 TCB 追溯 1

这里把两个参数传入了 prvInitialiseNewTask 函数中。

任务创建时 TCB 追溯 2

在 prvInitialiseNewTask 函数的最末尾把 TCB结构体的值,给到了 pxCreatedTask ,函数返回时,这个参数已经指向了创建的新任务的 TCB 结构体。

任务创建时 TCB 追溯 3

任务控制

vTaskDelay

  • 这个函数用于任务延时的
  • 是非阻塞延时,当任务调用他时,只有调用他的任务进入阻塞状态,此时会执行其他任务。
  • 当延时结束时,延时事件到来,会将被阻塞的任务切换回就绪状态。
  • 进入就绪状态不代表可以马上执行该任务,还要等待轮到她进入
  • 将 INCLUDE_vTaskDelay 定义为 1,此函数才可用。
    • 使能 vTaskDelay 函数

函数介绍

void vTaskDelay( const TickType_t xTicksToDelay );

参数:

  • xTicksToDelay ,调用任务应阻塞的 tick 周期数。

函数使用

// 一定要放在全局的位置,使栈空间永久存在

TaskHandle_t myTask01Handle;	// hal 库生产的是 osThreadId myTask01Handle; 两者没有区别 osThreadId 就是对 TaskHandle_t 起了别名
TaskHandle_t myTask02Handle;

// 任务调度函数
void StartTask01(void const * argument)
{
    for(;;)
    {
        printf("I'm Task01.\r\n");
        vTaskDelay(10);	// 自动进入阻塞状态,等待
    }
}

// 任务调度函数
void StartTask02(void const * argument)
{
	uint8_t i = 0;
    for(;;)
    {
        printf("I'm Task02.\r\n");
        vTaskDelay(10);	// 自动进入阻塞状态,等待
        // 当 i > 20 时,释放当前线程 ( StartTask02 )
        if(i++ > 20)
        {
            vTaskDelete(NULL);
        }
    }
}

void main(void)
{
    // 动态的创建一个任务啦,参数可以看前面的解释
    if(xTaskCreate((TaskFunction_t)StartTask01, "myTask01", 128, NULL, (UBaseType_t)osPriorityIdle, myTask01Handle) == pdPASS)
    {
        // 创建成功打印一下啦
    	printf("Create myTask01 OK!\r\n");
    }
    if(xTaskCreate((TaskFunction_t)StartTask02, "myTask02", 128, NULL, (UBaseType_t)osPriorityIdle, myTask02Handle) == pdPASS)
    {
        // 创建成功打印一下啦
    	printf("Create myTask02 OK!\r\n");
    }
    
    vTaskStartScheduler();   /* 启动任务,开启调度 */
    /* 永远不应该到达这里,因为现在控制权已经被调度器接管了 */
    while(1);
}

vTaskDelayUntil

  • INCLUDE_vTaskDelayUntil 必须被定义为 1 才能使用此函数。

    • 使能 vTaskDalayUntil 函数
  • 此函数与 vTaskDelay() 在一个重要的方面有所不同: 传入的时间参数是相对于 vTaskDelay() 被调用的时间, 而 vTaskDelayUntil() 会指定任务希望取消阻塞的 绝对 时间。

  • 具体来说:

    • vTaskDelay 函数是通过将 当前任务挂起一段时间来实现延迟 的,而在这段时间内,任务不会执行任何操作。这意味着,如果在任务挂起期间发生了某些事件,例如中断或其他高优先级任务的执行,那么任务将会 “丢失执行”,即任务的执行时间会相应地延迟。因此,vTaskDelay 不能保证任务的执行时间是精确的。
    • 相比之下,vTaskDelayUntil 函数是 通过计算时间差来实现延迟 的,它可以精确地控制任务的执行时间。使用 vTaskDelayUntil 的任务可以在指定的时间内暂停执行,然后在预期的时间内恢复执行。这种方式可以保证任务在预期的时间内执行,从而提高系统的实时性和性能。因此,使用 vTaskDelayUntil 的任务不会 “丢失执行”。
  • 使用说明:

    • 在使用 vTaskDelayUntil 函数时,我们需要首先初始化 pxPreviousWakeTime 变量,然后在函数中传入该变量的地址,以及需要等待的时间间隔 xTimeIncrement。
    • 函数会自动计算任务需要等待多长时间,然后阻塞任务直到该时间到达,然后任务会被自动唤醒。这种方式可以精确控制任务的执行时间,从而提高系统的实时性和性能。

函数介绍

void vTaskDelayUntil( TickType_t *pxPreviousWakeTime,
                      const TickType_t xTimeIncrement );

参数介绍:

  1. pxPreviousWakeTime,
    • 指向一个 TickType_t 类型变量的指针,用于保存任务最后一次解除阻塞的时间。
    • 这个变量需要在第一次使用前用当前时间进行初始化,可以通过调用 xTaskGetTickCount 函数获取当前时间。
    • 在 vTaskDelayUntil 函数内部,此变量会自动更新,以便在下一次调用时使用。
  2. xTimeIncrement,周期时间段,即任务需要等待的时间。
    • 该任务将在 (*pxPreviousWakeTime + xTimeIncrement) 时间解除阻塞。
    • 如果任务需要以固定的间隔期执行,可以使用相同的 xTimeIncrement 参数值调用 vTaskDelayUntil 函数。

函数使用

TaskHandle_t myTask01Handle;	// hal 库生产的是 osThreadId myTask01Handle; 两者没有区别 osThreadId 就是对 TaskHandle_t 起了别名
TaskHandle_t myTask02Handle;
TaskHandle_t myTask03Handle;

// 任务调度函数
void StartTask01(void const * argument)
{
    for(;;)
    {
        printf("I'm Task01.\r\n");
        vTaskDelay( 10 );	// 自动进入阻塞状态,等待
    }
}
// 任务调度函数
void StartTask02(void const * argument)
{
	// 创建一个
	TickType_t xLastWakeTime = xTaskGetTickCount();
    for(;;)
    {
        printf("I'm Task02.\r\n");
		// 绝对延时
		vTaskDelayUntil( &xLastWakeTime, 50 );
    }
}
// 任务调度函数
void StartTask03(void const * argument)
{
    for(;;)
    {
        printf("I'm Task03.\r\n");
		// 绝对延时
		vTaskDelay( 10 );
    }
}

void main(void)
{
    // 动态的创建一个任务啦,参数可以看前面的解释
    if(xTaskCreate((TaskFunction_t)StartTask01, "myTask01", 128, NULL, (UBaseType_t)osPriorityIdle, myTask01Handle) == pdPASS)
    {
        // 创建成功打印一下啦
    	printf("Create myTask01 OK!\r\n");
    }
    if(xTaskCreate((TaskFunction_t)StartTask02, "myTask02", 128, NULL, (UBaseType_t)osPriorityIdle, myTask02Handle) == pdPASS)
    {
        // 创建成功打印一下啦
    	printf("Create myTask02 OK!\r\n");
    }
    if(xTaskCreate((TaskFunction_t)StartTask03, "myTask03", 128, NULL, (UBaseType_t)osPriorityIdle, myTask03Handle) == pdPASS)
    {
        // 创建成功打印一下啦
    	printf("Create myTask03 OK!\r\n");
    }
    vTaskStartScheduler();   /* 启动任务,开启调度 */
    /* 永远不应该到达这里,因为现在控制权已经被调度器接管了 */
    while(1);
}

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

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

相关文章

[中间件漏洞]apache漏洞复现

目录 apache未知扩展名解析漏洞 漏洞复现 防范建议 AddHandler导致的解析漏洞 防范建议 Apache HTTPD 换行解析漏洞(CVE-2017-15715) 漏洞复现 防范建议 apache未知扩展名解析漏洞 Apache默认一个文件可以有多个以点分割的后缀,当最右边的后缀…

【LeetCode热题100】打卡第5天:最长回文子串

文章目录 最长回文子串⛅前言🔒题目🔑题解 最长回文子串 ⛅前言 大家好,我是知识汲取者,欢迎来到我的LeetCode热题100刷题专栏! 精选 100 道力扣(LeetCode)上最热门的题目,适合初识…

部署OA系统

文章目录 前言一、OA系统基础1.OA系统2.魔方OA3.OA系统架构4.部署OA系统 二、使用步骤总结 前言 部署OA系统,以魔方OA为例 一、OA系统基础 1.OA系统 办公自动化(Office Automation,简称OA),是将计算机、通信等现代化…

⑥电子产品拆解分析-食物电子秤

⑥电子产品拆解分析-食物电子秤 一、功能介绍二、电路分析以及器件作用三、原理图复现与学习1、电源电路2、按键电路3、其它接口电路 一、功能介绍 ①高精度0.1g称重;②内置锂电池和外加2个7号电池超长续航;③可进行克和盎司单位称重;④一键智…

Flask or FastAPI? Python服务端初体验

1. 引言 最近由于工作需要,又去了解了一下简单的python服务搭建的相关工作,主要是为了自己开发的模型或者工具给同组的人使用。之前介绍的针对于数据科学研究比较友好的一个可以展示的前端框架Streamlit可以说是一个利器。不过,随着ChatGPT的…

由前序和中序创建二叉树

算法分析 首先,前序是按照 根 -> 左子树 -> 右子树 这样的顺序来进行访问的,也就是说,前序给出的顺序一定是先给出根结点的,那么我们就可以根据前序的顺序来依次递归判断出每个子树的根结点了。 如下所示: 我…

源码角度分析多线程并发情况下数据异常回滚方案

一、 多线程并发情况下数据异常回滚解决方案 在需要多个没有前后顺序的数据操作情况下,一般我们可以选择使用并发的形式去操作,以提高处理的速度,但并发情况下,我们使用 Transactional 还能解决事务回滚问题吗。 例如有下面表结…

Go语言并发

Go语言并发学习目标 出色的并发性是Go语言的特色之一 • 理解并发与并行• 理解进程和线程• 掌握Go语言中的Goroutine和channel• 掌握select分支语句• 掌握sync包的应用 并发与并行 并发与并行的概念这里不再赘述, 可以看看之前java版写的并发实践; 进程和线程 程序、进程…

C语言3:根据身份证号输出生年月日和性别

18位身份证号码第7到10位为出生年份(四位数),第11到12位为出生月份,第13 到14位代表出生日期,第17位代表性别,奇数为男,偶数为女。 用户输入一个合法的身份证号,请输出用户的出生年月日和性别。(不要求较验…

Java数据结构之第十三章、字符串常量池

目录 一、创建对象的思考 二、字符串常量池(StringTable) 三、再谈String对象创建 一、创建对象的思考 下面两种创建String对象的方式相同吗? public static void main(String[] args) {String s1 "hello";String s2 "hello";String s3 …

C# | 线性回归算法的实现,只需采集少量数据点,即可拟合整个数据集

C#线性回归算法的实现 文章目录 C#线性回归算法的实现前言示例代码实现思路测试结果结束语 前言 什么是线性回归呢? 简单来说,线性回归是一种用于建立两个变量之间线性关系的统计方法。在我们的软件开发中,线性回归可以应用于数据分析、预测和…

每日一博 - 对称加密算法 vs 非对称加密算法

文章目录 概述一、对称加密算法常见的对称加密算法优点:缺点:Code 二、非对称加密算法常见的非对称加密算法优点:缺点:Code 概述 在信息安全领域中,加密算法是保护数据安全的重要手段。 加密算法可以分为多种类型&am…

【Linux】线程互斥 与同步

文章目录 1. 背景概念多个线程对全局变量做-- 操作 2. 证明全局变量做修改时,在多线程并发访问会出问题3. 锁的使用pthread_mutex_initpthread_metux_destroypthread_mutex_lock 与 pthread_mutex_unlock具体操作实现设置为全局锁 设置为局部锁 4. 互斥锁细节问题5.…

哈夫曼树(Huffman)【数据结构】

目录 ​编辑 一、基本概念 二、哈夫曼树的构造算法 三、哈夫曼编码 假如<60分的同学占5%&#xff0c;60到70分的占15%…… 这里的百分数就是权。 此时&#xff0c;效率最高&#xff08;判断次数最少&#xff09;的树就是哈夫曼树。 一、基本概念 权&#xff08;we…

Zabbix4.0 自动发现TCP端口并监控

java端口很多&#xff0c;每台机器上端口不固定&#xff0c;考虑给机器配置组不同的组挂载模版&#xff0c;相对繁琐。直接使用同一个脚本自动获取机器上java相关的端口&#xff0c;推送到zabbix-server。有服务端口挂了自动推送告警 一、zabbix-agent配置过程 1、用户自定义参…

Apache Doris :Rollup 物化视图

整理了一下目前开启虚拟机需要用到的程序, 包括MySQL,Hadoop,Linux, hive,Doris 3.5 Rollup ROLLUP 在多维分析中是“上卷”的意思&#xff0c;即将数据按某种指定的粒度进行进一步聚合。 1.求每个城市的每个用户的每天的总销售额 select user_id,city,date&#xff0c; sum(…

树的简单介绍

目录 树的概念 ​ 树的相关概念 树的表示 二叉树的概念 特殊的二叉树 二叉树的存储结构 总结 树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#…

Diffie-Hellman密钥交换协议(Diffie-Hellman Key Exchange,简称DHKE)

文章目录 Diffie-Hellman密钥交换协议&#xff08;Diffie-Hellman Key Exchange&#xff0c;简称DHKE&#xff09;一、密码学相关的数学基础1. 素数&#xff08;质数&#xff09;2. 模运算3. 费马小定理4. 对数5. 离散对数6. 椭圆曲线常见椭圆曲线1. NIST系列曲线secp256k1 2. …

Django实现人脸识别登录

Django实现人脸识别登录 Demo示例下载 1、账号密码登录 2、人脸识别登录 3、注册 4、更改密码 5、示例网站 点我跳转 一、流程说明 1、注册页面:前端打开摄像头,拍照,点击确定后上传图像 2、后端获取到图像,先通过face_recognition第三方库识别是否能够获取到人脸特征…

开闭原则正确姿势, 使用AOP优雅的记录日志, 非常的哇塞

&#x1f473;我亲爱的各位大佬们好&#x1f618;&#x1f618;&#x1f618; ♨️本篇文章记录的为 JDK8 新特性 Stream API 进阶 相关内容&#xff0c;适合在学Java的小白,帮助新手快速上手,也适合复习中&#xff0c;面试中的大佬&#x1f649;&#x1f649;&#x1f649;。 …