FreeRTOS信号量
前面介绍过,队列(queue)可以用于传输数据:在任务之间,任务和中断之间。
消息队列用于传输多个数据,但是有时候我们只需要传递一个状态,这个状态值需要用一个数值表示,比如:
- 卖家:做好了1个包子,做好了2个包子,做好了3个包子! 
- 买家:买了一个包子,包子数量减1 
- 这个停车位我占了,停车位减1 
- 我开车走了,停车位加1 
在这些情况下,我们只需要维护一个数值,使用信号量效率更高,更节省内存。
FreeRTOS信号量简介
信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问。其中,“同步”指的是任务之间的同步,即信号量可以使得一个任务等待另一个任务完成某件事后,才继续执行;而“有序访问”指的是对被多任务或中断访问的共享资源(如全局变量)的管理,当一个任务在访问(读取或写入)一个共享资源时,信号量可以防止其他任务或中断在这期间访问(读取或写入)这个共享资源。

FreeRTOS二值信号量
前面说过,信号量是基于队列实现的,二值信号量也不例外,二值信号量实际上就是一个队列长度为1的队列,在这种情况下,队列就只有空和满两种情况,这就是二值信号量,二值信号量通常用于互斥访问或任务同步,与互斥信号量类似,但是二值信号量有优先级反转的问题:优先级反转就是指,当一个高优先级任务因获取一个低优先级任务获取而处于没有资源状态的二值信号量时,这个高优先级的任务就将被阻塞,直到低优先级任务释放二值信号量,在此之前,如果有一个优先级介于高优先级和低优先级之间的中等优先级任务就绪,那么这个中等优先级任务就会抢占低优先级任务,那么,这三个任务中,高优先级任务反而要最后执行,这就是二值信号量带来的优先级反转问题。
FreeRTOS计数型信号量
计数型信号量和二值信号量类似,二值信号量相当于队列长度为1 的队列,因此二值信号量只能容纳一个资源,这也是为什么命名为二值信号量,而计数型信号量相当于队列长度大于0的队列,因此计数型信号零能容纳多个资源,这是在计数型信号量被创建的时候确定的。
计数型信号量适用于下面两种场合:
- 事件计数 
在这种场合下,每次事件发生后,在事件处理函数中释放计数型信号量(计数型信号量的资源数加1),其他等待事件发生的任务 获取计数型信号量(计数型信号量的资源数减1),这么以来等待事件发生的任务就可以在成功获取到计数型信号量之后执行相应的操作,在这种场合下,计数型信号量的资源数一般在创建时设置为0。
- 资源管理 
在这种场合下,计数型信号量的资源数代表着共享资源的可用数量,例如前面举例中的停车场中的空车位。一个任务想要访问共享资源,就必须先获取这个共享资源的计数型信号量,之后在成功获取了计数型信号量之后,才可以对这个共享资源进行操作,当然,在使用完共享资源之后也要释放这个共享资源的计数型信号量。在这种场合下,计数型信号量的资源数一般在创建时设置为受其管理的共享资源的最大可用数量。
信号量的特性
信号量的常规操作
信号量这个名字起的很恰当:
- 信号:起通知作用 
- 量:还可以表示资源的数量 
- 当“量”没有限制时,它就是“计数型信号量”(Counting Semaphores) 
- 当“量”只有0、1两个取值时,它就是“二值信号量”(Binary Seamphores) 
- 支持的动作:“give”给出资源,计数值加1,“take”获得资源,计数值减1 
计数信号量的典型场景是:
- 事件计数:事件产生时“give"信号量,让计数值加1;处理事件时要先”take“信号量,也就是获得信号量,让计数值减1 
- 资源管理:要想访问资源需要先”take“信号量,让计数值减1,用完资源后”give“信号量,让计数值加1 
信号量的”give“、”take“双方并不需要相同,可以用于生产者-消费者场合;
- 生产者为任务A,B,消费者为任务C,D 
- 一开始信号量的计数值为0,如果任务C,D想获得信号量,会有两种结果: 
- 阻塞:买不到东西咱就等等吧,可以定个闹钟(超时时间) 
- 即可返回失败:不等 
- 任务A,B可以生产资源,就是让信号量的计数值增加1,并且把等待这个资源的顾客唤醒 
- 唤醒谁?谁优先级高就唤醒谁,如果大家优先级都一样,就唤醒等待时间最长的人 
二值信号量和计数型信号量的唯一差别:就是计数值的最大值被限定为1

信号量跟队列的对比
| 队列 | 信号量 | 
| 可以容纳多个数据,创建队列时有2部分内存:队列结构体、存储数据的空间 | 只有计数值,无法容纳其他数据 创建信号量时,只需要分配信号量结构体 | 
| 生产者:没有空间存入数据时可以阻塞 | 生产者:用于不阻塞,计数值已经达到最大值时返回失败 | 
| 消费者:没有数据时可以阻塞 | 消费者:没有资源时可以阻塞 | 
两种信号量对比
| 二进制信号量 | 计数型信号量 | 
| 被创建时初始值为0 | 被创建时初始值可以设定 | 
| 其他操作是一样的 | 其他操作是一样的 | 
二值信号量
二值信号量的本质是一个队列长度为1的队列,该队列就只有空和满两种情况,这就是二值。
二值信号量通常用于互斥访问或任务同步,与互斥信号量比较类似,但是二值信号量有可能会导致优先级翻转的问题,所以二值信号量更适合用于同步。

二值信号量相关API函数
使用二值信号量的过程:创建二值信号量->释放二值信号量->获取二值信号量
使用信号量时,先创建,然后去添加资源、获得资源,使用句柄来表示一个信号量
| 函数 | 描述 | 
| xSemaphoreCreateBinary() | 使用动态方式创建二值信号量 | 
| xSemaphoreCreateBinaryStatic() | 使用静态方式创建二值信号量 | 
| xSemaphoreGive() | 释放二值信号量 | 
| xSemaphoreGiveFromISR() | 在中断中释放信号量 | 
| xSemaphoreTake() | 获取信号量 | 
| xSemaphoreTakeFromISR() | 在中断中获取信号量 | 
这些函数其实都可以在semphr.h头文件中找到,它们都是一些宏定义,本质上用的其实都是Queue的函数
创建二值信号量
/*
创建一个二进制信号量,返回它的句柄
此函数内部会分配信号量结构体
返回值:返回句柄,非NULL表示成功可以看出,动态创建二值信号量实际上是调用了函数xQueueGenericCreate()
创建了一个队列长度为1且队列项目大小为信号量队列项目大小的二值信号量类型队列
*/
#define  xSemaphoreCreateBinary()   xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )/*
创建一个二进制信号量,返回它的句柄
此函数无需动态分配内存,所以需要现有一个StaticSemaphore_t结构体,并传入它的指针
返回值:返回句柄,非NULL表示成功从 上 面 的 代 码 中 可 以 看 出 , 函 数 xSemaphoreCreateStatic() 实 际 上 是 调 用 了 函 数
xQueueGenericCreateStatic()创建了一个队列长度为 1 且队列项目大小为信号量队列项目大小的
二值信号量类型队列,需要用户手动分配提供创建二值信号量的内存
*/
#define xSemaphoreCreateBinaryStatic( pxStaticSemaphore )   xQueueGenericCreateStatic( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, NULL, pxStaticSemaphore, queueQUEUE_TYPE_BINARY_SEMAPHORE )获取信号量
/*
此函数用于获取信号量,如果信号量处于没有资源的状态,那么此函数可以选择将任务进
行阻塞,如果成功获取了信号量,那信号量的资源数将会减 1。该函数实际上是一个宏定义
*/
#define xSemaphoreTake( xSemaphore, xBlockTime )    xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue,TickType_t xTicksToWait )
{BaseType_t xEntryTimeSet = pdFALSE;TimeOut_t xTimeOut;Queue_t * const pxQueue = xQueue;#if ( configUSE_MUTEXES == 1 )BaseType_t xInheritanceOccurred = pdFALSE;#endif/* Check the queue pointer is not NULL. */configASSERT( ( pxQueue ) );/* 信号量类型队列的项目大小为0 */configASSERT( pxQueue->uxItemSize == 0 );/* Cannot block if the scheduler is suspended. */#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ){configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );}#endif/*lint -save -e904 This function relaxes the coding standard somewhat to allow return* statements within the function itself.  This is done in the interest* of execution time efficiency. */for( ; ; ){    /*进入临界区*/taskENTER_CRITICAL();{/* 获取信号量的资源数 */const UBaseType_t uxSemaphoreCount = pxQueue->uxMessagesWaiting;/* 判断信号量是否有资源 */if( uxSemaphoreCount > ( UBaseType_t ) 0 ){traceQUEUE_RECEIVE( pxQueue );/* 更新信号量的资源数 */pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1;#if ( configUSE_MUTEXES == 1 ){if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){/* 设置互斥信号量的持有者并更新互斥信号量的持有次数 */pxQueue->u.xSemaphore.xMutexHolder = pvTaskIncrementMutexHeldCount();}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_MUTEXES *//* 判断信号量的获取阻塞任务列表中是否有任务 */if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ){/*将阻塞任务从信号量获取阻塞任务列表中移除*/if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ){/*根据需要进行任务切换*/queueYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}taskEXIT_CRITICAL();return pdPASS;}/*信号量没有资源*/else{/*判断是否不选择阻塞等待信号量*/if( xTicksToWait == ( TickType_t ) 0 ){/* 此宏用于启用互斥信号量 */#if ( configUSE_MUTEXES == 1 ){configASSERT( xInheritanceOccurred == pdFALSE );}#endif /* configUSE_MUTEXES *//* 退出临界区*/taskEXIT_CRITICAL();traceQUEUE_RECEIVE_FAILED( pxQueue );return errQUEUE_EMPTY;}/*选择阻塞等待信号量*/else if( xEntryTimeSet == pdFALSE ){/* 队列满,任务需要阻塞记录此时系统节拍计数器 值和溢出次数用于下面对阻塞时间进行补偿*/vTaskInternalSetTimeOutState( &xTimeOut );xEntryTimeSet = pdTRUE;}else{/* Entry time was already set. */mtCOVERAGE_TEST_MARKER();}}}taskEXIT_CRITICAL();/* 挂起任务调度器 */vTaskSuspendAll();/*信号量队列上锁*/prvLockQueue( pxQueue );/* 判断阻塞时间补偿后,是否还需要阻塞 */if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){/* 判断队列是否为空 */if( prvIsQueueEmpty( pxQueue ) != pdFALSE ){traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );#if ( configUSE_MUTEXES == 1 ){if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){taskENTER_CRITICAL();{/*进行优先级继承,这是互斥信号量用于解决优先级翻转问题的*/xInheritanceOccurred = xTaskPriorityInherit( pxQueue->u.xSemaphore.xMutexHolder );}taskEXIT_CRITICAL();}else{mtCOVERAGE_TEST_MARKER();}}#endif /* if ( configUSE_MUTEXES == 1 ) *//*将任务添加到队列写入阻塞任务列表中进行阻塞*/vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );prvUnlockQueue( pxQueue );/*恢复任务调度器*/if( xTaskResumeAll() == pdFALSE ){portYIELD_WITHIN_API();}else{mtCOVERAGE_TEST_MARKER();}}/*队列不为空*/else{/* There was no timeout and the semaphore count was not 0, so* attempt to take the semaphore again. */prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();}}else{/* Timed out. */prvUnlockQueue( pxQueue );( void ) xTaskResumeAll();/* If the semaphore count is 0 exit now as the timeout has* expired.  Otherwise return to attempt to take the semaphore that is* known to be available.  As semaphores are implemented by queues the* queue being empty is equivalent to the semaphore count being 0. */if( prvIsQueueEmpty( pxQueue ) != pdFALSE ){#if ( configUSE_MUTEXES == 1 ){/* xInheritanceOccurred could only have be set if* pxQueue->uxQueueType == queueQUEUE_IS_MUTEX so no need to* test the mutex type again to check it is actually a mutex. */if( xInheritanceOccurred != pdFALSE ){taskENTER_CRITICAL();{UBaseType_t uxHighestWaitingPriority;/* This task blocking on the mutex caused another* task to inherit this task's priority.  Now this task* has timed out the priority should be disinherited* again, but only as low as the next highest priority* task that is waiting for the same mutex. */uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout( pxQueue );vTaskPriorityDisinheritAfterTimeout( pxQueue->u.xSemaphore.xMutexHolder, uxHighestWaitingPriority );}taskEXIT_CRITICAL();}}#endif /* configUSE_MUTEXES */traceQUEUE_RECEIVE_FAILED( pxQueue );return errQUEUE_EMPTY;}else{mtCOVERAGE_TEST_MARKER();}}} /*lint -restore */
}释放信号量
#define xSemaphoreGive( xSemaphore )    xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )这里其实和上面的获取是类似的,碍于篇幅有限,不给出源码。
| 在任务中使用 | 在ISR中使用 | |
| give | xSemaphoreGive | xSemaphoreGiveFromISR | 
| take | xSemaphoreTake | xSemaphoreTakeFromISR | 
删除
对于动态创建的信号量,不再需要使用它们时,可以删除它们以回收内存
vSemaphoreDelete可以删除二值信号量,计数型信号量,函数原型如下:
#define vSemaphoreDelete( xSemaphore )               vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) )计数型信号量
计数型信号量相当于队列长度大于1的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定。
计数型信号量相关API函数
| 函数 | 描述 | 
| xSemaphoreCreateCounting() | 使用动态方法创建计数型信号量 | 
| xSemaphoreCreateCountingStatic() | 使用静态方法创建计数型信号量 | 
| uxSemaphoreGetCount() | 获取信号量的计数值 | 
计数值信号量的获取和释放和二值信号量的相同,这里不再赘述。
计数型信号量创建
/*
uxMaxCount:计数值的最大值限定
uxInitialCount:计数值的初始值
NULL:创建失败
其他值:创建成功返回计数型信号量的句柄
*/ 
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )   xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )获取信号量当前计数值大小
/*
xSemaphore:信号量句柄
返回值:整数,当前信号量的计数值大小
*/
#define uxSemaphoreGetCount( xSemaphore )             uxQueueMessagesWaiting( ( QueueHandle_t ) ( xSemaphore ) )
个人总结
- 信号量是一种特殊的队列,其中二值信号量是长度为1的队列,计数型信号量是长度大于1的队列 
- 使用信号量,只是传递一个状态,而不是像队列一样可以缓存数据,然后进行数据交流,信号量相比于队列更加轻量化,只需要创建信号量结构体,队列不仅需要申请创建队列的结构体还包括存储数据的缓存区。 
- 二值信号量其实是一种特殊的计数型信号量,只有0和1两个计数值,常常用于互斥访问和任务间同步,但是二值信号量会导致优先级翻转的问题,互斥量被引申出解决该问题,所以二值信号量最合适用于任务同步。 
- 其实,不管是信号量,队列,互斥量,事件组等IPC(在线程通信)都是这样,都要先创建一个中间结构体,任务之间通过该结构体传输信息,进行交流,这是一种间接的任务之间通信方式。 
相关文章:
 
FreeRTOS信号量
前面介绍过,队列(queue)可以用于传输数据:在任务之间,任务和中断之间。消息队列用于传输多个数据,但是有时候我们只需要传递一个状态,这个状态值需要用一个数值表示,比如:…...
 
Leetcode.2385 感染二叉树需要的总时间
题目链接 Leetcode.2385 感染二叉树需要的总时间 Rating : 1711 题目描述 给你一棵二叉树的根节点 root,二叉树中节点的值 互不相同 。另给你一个整数 start。在第 0分钟,感染 将会从值为 start的节点开始爆发。 每分钟,如果节点…...
 
[蓝桥杯 2022 国 B] 卡牌(贪心/二分)
题目传送门 该题第一思路是想去模拟题目中所描述的过程 这里我选择从大到小遍历可能凑出的牌套数,计算凑出它需要补的牌数以及判断是否会超出能补的牌数 #include<iostream> #include<climits> #include<vector> #include<algorithm> #def…...
 
1301:大盗阿福
经典的dp打家劫舍问题状态设计dp[i][0]:在前i个店铺中选,且不选第i家的最大和dp[i][1]:在前i个店铺中选,且选第i家的最大和状态转移dp[i][0] max(dp[i-1][1], dp[i-1][0];第i家店不选,那么我们可以选第i-1个店 也可以…...
 
Netty——序列化的作用及自定义协议
序列化的作用及自定义协议序列化的重要性大小对比效率对比自定义协议序列化数据结构自定义编码器自定义解码器安全性验证NettyClientNettyServerNettyClientTestHandlerNettyServerTestHandler结果上一章已经说了怎么解决沾包和拆包的问题,但是这样离一个成熟的通信…...
一起Talk Android吧(第五百零五回:如何调整组件在约束布局中的大小)
文章目录 背景介绍调整方法各位看官们大家好,上一回中咱们说的例子是"如何调整组件在约束布局中的位置",这一回中咱们说的例子是" 如何调整组件在约束布局中的大小"。闲话休提,言归正转, 让我们一起Talk Android吧! 背景介绍 在使用约束(constraintl…...
 
【数据库】数据库的完整性
第五章 数据库完整性 数据库完整性 数据库的完整性是指数据的正确性和相容性 数据的正确性是指数据是符合现实世界语义,反映当前实际状况的数据的相容性是指数据库的同一对象在不同的关系中的数据是符合逻辑的 关系模型中有三类完整性约束:实体完整性…...
 
基因净化车间装修设计方案SICOLAB
基因净化车间的设计方案应该根据实际需求进行定制,以下是一些规划建设要点和洁净设计要注意的事项:一、净化车间规划建设要点:(1)基因车间的面积应该根据实验项目的规模进行规划,包括充足的操作区域和足够的…...
 
java 内部类的四种“写法”
基本介绍语法格式分类成员内部类静态内部类局部内部类匿名内部类(🐂🖊)一、基本介绍 : 1.概述当一个类的内部又完整地嵌套了另一个类时,被嵌套于内部的“内核”我们称之为“内部类”(inner class);而包含该…...
 
【python】main方法教程
嗨害大家好鸭! 我是小熊猫~ 首先 if name "main": 可以看成是python程序的入口, 就像java中的main()方法, 但不完全正确。 事实上python程序是从上而下逐行运行的, 在.py文件中, 除…...
 
公司对不同职级能力抽象要求的具体化
要先把当前级别要求的能力提升到精通,然后尝试做下一级别的事情。 但可能不确定高一级的能力要求究竟怎样,不同Title,如“工程师”“高级工程师”和“资深工程师”等。但这样 Title 对我们理解不同级别的能力要求,完全无用。“高…...
Java之MinIO存储桶和对象API使用
环境搭建 创建一个 maven项目,引入依赖: <!-- minio依赖--><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.3.3</version></dependency><!-- 官方 minio…...
如何用java实现同时进行多个请求,可以将它们并行执行,从而减少总共的请求时间。
1.使用线程池 通过使用Java提供的线程池,可以将多个请求分配到不同的线程中并行执行。可以通过创建固定数量的线程池,然后将请求分配给线程池来实现。线程池会自动管理线程的数量和复用,从而减少了线程创建和销毁的开销,提高了程序…...
 
高端装备的AC主轴头结构
加工机器人的AC主轴头和位置相关动力学特性1. 位置依赖动态特性及其复杂性2. AC主轴头2.1 常见主轴头摆角结构2.2 摆动机构3. 加装AC主轴头的作用和局限性4. 切削机器人的减速器类型5. 其他并联结构形式参考文献资料1. 位置依赖动态特性及其复杂性 However, FRF measurements …...
 
【Proteus仿真】【51单片机】粮仓温湿度控制系统设计
文章目录一、功能简介二、软件设计三、实验现象联系作者一、功能简介 本项目使用Proteus8仿真51单片机控制器,使用声光报警模块、LCD1602显示模块、DHT11温湿度模块、继电器模块、加热加湿除湿风扇等。 主要功能: 系统运行后,LCD1602显示传…...
 
【LINUX】环境变量以及main函数的参数
文章目录前言环境变量常见环境变量:设置环境变量:和环境变量相关的命令:环境变量的组织方式:获取环境变量环境变量可以被子进程继承环境变量总结main函数的参数前言 大家好久不见,今天分享的内容是环境变量和main函数…...
 
使用Pyparsing为嵌入式开发定义自己的脚本语言
Python在嵌入式开发中也很流行生成实用脚本。Pyparsing还允许你轻松地定义在Python上下文中运行的定制脚本语言。Python实现的系统旨在能够独立执行用户传递的一系列命令。你希望系统以脚本的形式接收命令。用户应该能够定义条件。这种对通信中逻辑元素的最初简单的声音要求&am…...
C win32基础学习(二)
上一篇我们已经介绍了关于窗口程序的一些基本知识。从本篇开始我们将正式进入C win32的学习中去。 正文 窗口创建过程 定义WinMain函数 定义窗口处理函数(自定义,处理消息) 注册窗口类(向操作系统写入一些数据) 创建窗口(内存…...
理论五:控制反转、依赖反转、依赖注入,这三者有何区别和联系?
关于SOLID原则,我们已经学过单一职责、开闭、里式替换、接口隔离这四个原则。今天,我们再来学习最后一个原则:依赖反转原则。在前面几节课中,我们讲到,单一职责原则和开闭原则的原理比较简单,但是,想要在实践中用好却比较难。而今天我们要讲到的依赖反转原则正好相反。这个原则…...
 
读书笔记//《数据分析之道》
出版时间:2022年 作者曾在互联网大厂做数据分析。从举例可以洞见作者的工作经历。 点评:作者在数据分析领域非常资深,尝试在书中提供一个数据分析工作框架参考。书本内容有点感觉是ppt的集合,辅以案例说明。不过,干货还…...
 
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
 
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
 
剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
 
自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
 
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
 
(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
 
[论文阅读]TrustRAG: Enhancing Robustness and Trustworthiness in RAG
TrustRAG: Enhancing Robustness and Trustworthiness in RAG [2501.00879] TrustRAG: Enhancing Robustness and Trustworthiness in Retrieval-Augmented Generation 代码:HuichiZhou/TrustRAG: Code for "TrustRAG: Enhancing Robustness and Trustworthin…...
