【RTOS学习】同步与互斥 | 队列
🐱作者:一只大喵咪1201
🐱专栏:《RTOS学习》
🔥格言:你只管努力,剩下的交给时间!
同步与互斥 | 队列
- 🍉同步与互斥
- 🍦同步
- 🍦互斥
- 🍉队列
- 🧊环形缓冲区
- 🧊读写任务链表
- 🧊操作队列的函数
- 🧊使用队列
- 🍉总结
🍉同步与互斥
FreeRTOS是一个实时操作系统,是一个多任务系统,任务之间存在同步关系,如协调工作进度(同步),还有互斥关系,就像争用会议室(互斥)。
到底什么是同步什么是互斥呢?拿一个生活中上厕所的例子来说:“等我用完厕所,你再用厕所”。
- 同步:我正在用厕所,你得等会儿再用。
- 互斥:我正在用厕所,你不能进来。
只看我和你,必须得等我用完厕所你才能用,我和你之间有必然的先后顺序,存在协同关系,这就是同步。
只看厕所,同一时刻只能进去一个人,我和你之间就是竞争关系,这就是互斥。
🍦同步
举一个同步关系的代码例子:有两个任务,任务1进行计算,任务2打印计算结果,但是必须等任务1计算完成才能打印,这是同步关系。
第一种方式:
如上图,创建两个任务,优先级都是1,在任务1中进行5000000次加法运算,完成后将完成标志位flagCalcEnd
置一,并且自杀(为了方便实验)。
在任务2中,不断检测计算完成标志位flagCalcEnd
,当变成1以后,打印五百万次求和的结果。
如上图结果所示,在耗时3秒钟左右时,计算完成标志位从0变成了1,说明此时计算完成,而且通过串口打印出了运算结果。
在这个计算过程中,任务1和任务2按照时间片轮转的方式在执行,任务1在进行计算,任务2只是判断一下falgCalcEnd
是否为1,但是它执行的时间和任务1是一样的,都是一个时间片。
- 此时计算完成耗时3秒左右。
第二种方式:
如上图,先只创建任务1,然后进行计算,当计算完成以后,在任务1中创建任务2用来打印计算结果,并且将flagCalcEnd
标志位置一。
如上图,这次运行结果和前面相比,其他都相同,只有耗费时间不同,变成了1.5秒左右,缩短了一倍。
- 此时计算完成耗时1.5秒左右。
虽然上面两种方式都能实现同步的目的,但是显然第二种方式效率更高,也就是在任务1进行计算的时候,任务2应该处于阻塞状态,不应该和任务1竞争CPU资源。
当任务1计算完成以后,唤醒任务2来打印结果即可,这样的同步关系才是高效合理的。
但是第二种方式在代码结构上就让任务1和任务2在串行执行,而在FreeRTOS中,多任务之间应该“并行”执行,所以还是要用第一种方式,但是要让任务2在任务1计算期间处于阻塞状态,把CPU资源完全让出来。
该用什么样的方式实现同步呢?后面本喵详细讲解。
🍦互斥
再举一个互斥关系的例子:两个任务使用一个串口打印信息,这两个串口就是互斥关系。
如上图,创建一个函数TaskGenericFunction
,里面使用串口打印启动任务时传入的参数,创建两个新任务都调用这一个函数,任务的优先级都是1,两个任务打印各自的运行信息。
如上图,此时看到的结果中,打印出来的数据非常混乱,一句话中既有属于任务3打印的内容,也有任务4打印的内容,这是因为两个任务没有互斥的使用串口。
如上图,增加一个串口使用标志flagUARTused
,只有标志位是0的时候,任务才能去使用串口,在使用之前将标志位置一,表示有任务在使用,另一个任务就无法使用了。
使用完毕以后,再将标志位清0,另一个任务才可以使用,此时就实现了两个任务之间的互斥。
如上图,此时任务3和任务4打印出的信息就是独立的,没有混杂在一起,主要就是因为两个任务之间实现了互斥。
- 每个任务使用完串口以后,主动延时一个
Tcik
,这是为了削弱当前任务的竞争力。
如果不延时的话,当前任务使用完串口,将标志位置0后,它仍然在占用CPU资源,另一个任务还没有被调度,当前任务就再次将标志位置一使用串口了,这样就会导致任务4一直在使用串口,有兴趣的小伙伴自己实验一下,本喵就不演示了。
上面的方式真的实现互斥了吗?
如上图,考虑一个极端一点的情况,任务3完成if判断以后,但是还没有将标志位置一,如上图红色线条所在位置,此时任务3被切走了。
CPU开始执行任务4了,由于此时标志位并没有被置1,所以任务4也可以使用串口,此时任务3和任务4的互斥关系就不存在了,它两都可以使用串口。
- 这样的方式运行时间长了以后,上面的这种情况就会成为必然。
除此之外,一个任务在使用串口的时候,另一个任务也会被调度,但是它只是检测一下标志位是否为0,和同步例子中的问题一样,也会浪费CPU资源。
所以在一个任务使用串口的时候,另一个任务也应该处于阻塞状态,当前任务使用完毕以后,唤醒另一个任务。
到底该用什么样的方式实现互斥呢?后面本喵详细讲解。
同步与互斥经常放在一起讲,是因为它们之间的关系很大,“互斥”操作可以使用“同步”来实现,我“等”你用完厕所,我再用厕所。
串口例子中,一个任务在使用串口,另一个任务必须等当前任务使用完毕后才能使用。
- 这就是用“同步”来实现“互斥”。
🍉队列
FreeRTOS有一套实现同步与互斥的方案,同时也解决了前面本喵所说的问题,第一种方案就是使用队列结构:
如上图所示队列模型,这是一个先进先出的结构,左边的任务A和任务B是生产者,也可以是更多任务,生产者负责生成数据。
右边的任务C和任务D是消费者,也可以是更多任务,消费者负责消费(读取)队列中的数据。
- 只要队列中有空位,生产者就可以生成数据。
- 只要队列中有数据,消费者就可以消费数据。
- 生产任务和消费任务访问队列时,所有任务之间都是互斥的,只能有一个任务访问队列。
队列是如何实现同步与互斥的呢?接下来就来看一下它的结构:
如上图代码所示,在queue.c
中定义了一个队列结构体,类型重命名为xQUEUE
,它有一个环形缓冲区,还有两个链表,一个存放生产者任务的TCB节点,一个存放消费者任务的TCB节点,还有队列长度,以及队列中每个数据的大小等属性。
🧊环形缓冲区
写:
如上图所示,队列中的环形缓冲区是通过指针pcHead
和pcWriteTo
来维护的,所以这两个指针就代表着环形缓冲区。
当队列刚创建的时候,环形缓冲区是空的,假设创建的队列长度是N
,队列中每个元素的大小是sizeof(Item)
,头指针pcHead
和写指针pcWiteTo
指向环形队列的头部。
头指针pcHead
永远指向头部不发生变化,当有任务要向环形队列中写入数据时,写入pcWriteTo
指针指向的位置,然后pcWriteTo
指针向后移动,指向下一个要写入的位置。
当向队列中写入第N个数据时,pcWriteTo
指针就会通过取模运算重新指向pcHead
指向的队列头部,向该位置写入数据。
读:
如上图所示,读取队列时,通过读指针pcReadFrom
来控制读取的位置,但是从队列的结构体中并没有看到读指针pcReadFrom
,因为它在联合体union u
中:
如上图,在队列中的联合体成员中,QueuePointers_t
成员中存在读指针pcReadFrom
成员变量。
和pcWriteTo
不同的是,读指针pcReadFrom
的起始位置在下标为N-1
处,读指针pcReadFrom
指向的是上一次读取数据的位置。
有任务来读取数据时,pcReadFrom
先向后移动,如果移动前指向最后一个位置,那么同样通过取模运行指向环形队列头部,然后将该位置的数据取出。
有了读指针和写指针后,就可以判断环形缓冲区是否满了,在写数据时,当pcWriteTo == pcReadFrom
的时候,就说明队列满了,不能继续写数据了。
在读数据时,当pcReadFrom == pcWriteTo
的时候,说明队列空了,无法继续读取数据了。
🧊读写任务链表
队列有满或者空的情况,此时FreeRTOS
是怎么处理生产者任务或者消费者任务的呢?
如上图,在队列结构体中有两个链表,分别是写任务链表xTaskWaitingToSend
和读任务链表xTaskWaitingToReceive
,这是两个阻塞链表,管理处于阻塞状态的读写任务。
当生产者任务向队列中写数据时发现pcWriteTo==pcReadFrom
,FreeRTOS
就将这个生产者任务设置成阻塞状态并且放入xTaskWaitingToSend
链表中,当队列中有空闲位置了,就从xTaskWaitingToSend
中唤醒一个任务向队列中写数据
当消费者任务从队列中读取数据时发现pcReadFrom==pcWriteTo
,FreeRTOS
就将这个消费者任务设置成阻塞状态并放入xTaskWaitingToReceive
阻塞链表中,当队列中有数据了,就从xTaskWaitingToReceive
唤醒一个任务从队列中读数据。
此时就做到了生产者任务和消费者任务之间的同步。
如上图,生产者任务向队列中写数据时,需要调用xQueueSend
函数,该函数内部会先将所有中断都关闭,此时Tcik
就无法产生中断,也就不会调度其他任务,这段时间CPU就只能执行这个任务,数据写入完毕后,再打开所有中断,恢复Tick
对其他任务的调度。
消费者任务从队列中读取任务时也一样,也会先关闭所有中断禁止调度其他任务,数据读取完毕后再打开中断恢复调度,本喵这里就不给大家看源码了。
此时就做到了生产者任务和消费者任务之间的互斥,同一时刻,只能有一个任务来访问队列。
既然读取和写入数据的任务个数没有限制,那么当多个任务读取空队列或者多个任务向满队列中写入数据时,这些任务都会进入阻塞状态,此时就有多个任务在同一个链表中。
当队列中有数据时或者有空位时,哪个任务会进入就绪态状态?
- 优先级最高的任务
- 如果大家优先级相同,那么等待时间最久的任务会进入就绪状态。
🧊操作队列的函数
使用队列的流程:创建队列、写队列、读队列、删除队列。
创建:
- 动态分配内存:队列的内存在函数内部使用
malloc
动态分配。
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
- uxQueueLength:队列长度,最多能存放多少个数据(Item)。
- uxItemSize:每个数据(Item)的大小,以字节为单位。
- 返回值:成功返回队列句柄,失败返回NULL,一般都是因为内存不足。
- 静态分配内存:队列的内存要用户事先分配好。
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength,UBaseType_t uxItemSize,uint8_t *pucQueueStorageBuffer,StaticQueue_t *pxQueueBuffer);
- uxQueueLength:队列长度,最多能存放多少个数据(Item)。
- uxItemSize:每个数据(Item)的大小,以字节为单位。
- pucQueueStorageBuffer:如果uxItemSize非0,pucQueueStorageBuffer必须指向一个uint8_t数组,此数组大小至少为
uxQueueLength * uxItemSize
。- pxQueueBuffer:必须创建一个
StaticQueue_t
结构体,用来保存队列的数据结构。- 返回值:成功返回队列句柄,失败返回NULL,一般都是因为内存不足。
写队列:
/* 向队列尾部写入数据 */
BaseType_t xQueueSend(QueueHandle_t xQueue,const void *pvItemToQueue,TickType_t xTicksToWait);
/* 向队列尾部写入数据 */
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,const void *pvItemToQueue,TickType_t xTicksToWait);
虽然这两个函数名称不一样,但是作用是一样的,都是向队列尾部写入数据。
/* 往队列头部写入数据 */
BaseType_t xQueueSendToFront(QueueHandle_t xQueue,const void *pvItemToQueue,TickType_t xTicksToWait);
这些函数用到的参数都是相同的:
- xQueue:队列句柄,要写哪个队列。
- pvItemToQueue:数据指针,这个数据会被拷贝到队列中。
- xTicksToWait:如果队列满则无法写入新数据,可以让任务进入阻塞状态,
xTicksToWait
表示阻塞的最大时间(Tick Count)。如果被设为0,无法写入数据时函数会立刻返回;如果被设为portMAX_DELAY
,则会一直阻塞直到有空间可写。- 返回值:
pdPASS
:从队列读出数据入,errQUEUE_EMPTY
:读取失败,因为队列空了。
- 写数据时,是将用户传入指针所指数据拷贝到队列中,所以必须传指针。
读队列:
BaseType_t xQueueReceive( QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait );
- xQueue:队列句柄,要读哪个队列。
- pvBuffer:bufer指针,队列的数据会被复制到这个buffer。
- xTicksToWait :等待时间,和写队列时一样。
- 返回值:
pdPASS
:从队列读出数据入,errQUEUE_EMPTY
:读取失败,因为队列空了。
删除:
void vQueueDelete( QueueHandle_t xQueue );
只能删除使用动态方法创建的队列,它会释放内存。
复位:
BaseType_t xQueueReset( QueueHandle_t pxQueue);
队列刚被创建时,里面没有数据;使用过程中可以调用该函数把队列恢复为初始状态,此函的返回值是pdPASS
,必定复位成功。
查询:
/* 返回队列中可用数据的个数 */
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );/* 返回队列中可用空间的个数 */
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );
覆盖:
BaseType_t xQueueOverwrite(QueueHandle_t xQueue,const void * pvItemToQueue);
当队列长度为1时,可以使用该函数来覆盖数据。注意,队列长度必须为1。当队列满时,该函数会覆盖里面的数据,这也意味着该函数不会被阻塞。
偷看:
BaseType_t xQueuePeek(QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait);
如果想让队列中的数据供多方读取,也就是说读取时不要移除数据,要留给后来人。那么可以使用该函数来"窥视",该函数会从队列中复制出数据,但是不移除数据。这也意味着,如果队列中没有数据,那么"偷看"时会导致阻塞;一旦队列中有数据,以后每次"偷看"都会成功。
后面几个函数的参数和前面几个一样,本喵没有再介绍,照猫画虎即可。
🧊使用队列
基本使用:
现在来解决开篇时同步例子中的缺陷:
如上图,创建一个队列,使用一个全局队列句柄xQueueCalcHandle
来接收返回值,再创建两个任务,优先级都是1。
如上图,任务1进行五百万次运算,计算完成后将结果放入队列中,任务2从队列中读取任务1的计算结果。
如上图,可以看到,整个计算过程花费1.5秒左右,和我们前面串行方式耗时一样,但是此时是同时创建的两个任务,两个任务在并行执行。
- 读取数据的任务,在队列中没有数据时处于阻塞状态,不会竞争CPU资源,当队列中有数据时才会唤醒它。
分辨数据源:
当有多个发送任务,通过同一个队列发出数据,接收任务如何分辨数据来源?数据本身带有"来源"信息,比如写入队列的数据是一个结构体,结构体中的IDataSouceID
用来表示数据来源:
typedef struct {ID_t eDataID;int32_t lDataValue;
}Data_t;
不同的发送任务,先构造好结构体,填入自己的 eDataID
,再写队列;接收任务读出数据后,根据eDataID
就可以知道数据来源了,如下图所示:
- CAN任务发送的数据:eDataID=eMotorSpeed
- HMI任务发送的数据:eDataID=eSpeedSetPoint
如上图,CAN任务和HMI任务将结构体数据写入到队列中后,Controller任务在读取到数据时,就可以通过结构体中的eDataID
成员知道是哪个任务发送来的数据。
如上图,枚举数据来源,定义传输数据的结构体,将两个发送任务要发送的数据放在一个数组中并初始化。
如上图,创建俩个任务用来发送数据,分别是CAN Task
和HMI Task
,优先级都是2,再创建一个接收数据的任务ReceiverTask
,优先级是1,只有两个发送任务将队列写满阻塞后,接收任务才能从队列中读取任务。
如上图所示,两个发送任务分别发送自己的结构体数据到队列中,接收任务从队列中读取结构体数据,通过eDataID
成员确定数据源并且打印数据。
如上图运行结果所示,两个发送任务的优先级都是2,所以它两先执行,又因为HMI
任务是后创建的,所以先运行,瞬间就将队列写满了,这个过程中CAN
任务还没有来得及被调度就因为队列满而被阻塞了。
接收任务开始读取后,先读取的是队列中的HMI
任务的数据,此时队列出现空位,所以唤醒了CAN
任务写队列,该数据排在最后,有空位后再唤醒HMI
任务,如此往复。
所以读取的前五个都是HMI
任务写的数据,之后就是CAN
任务和HMI
任务写的数据交替出现。
传输大块数据:
FreeRTOS的队列使用拷贝传输,也就是要传输uint32_t时,把4字节的数据拷贝进队列;要传输一个8字节的结构体时,把8字节的数据拷贝进队列。
如果要传输1000字节的结构体呢?写队列时拷贝1000字节,读队列时再拷贝1000字节?先不说浪不浪费内存,效率必然会很低!
这时候,我们要传输的是这个巨大结构体的地址或者是一个字符串的地址,把它的地址写入队列,对方从队列得到这个地址,使用地址去访问那1000字节的数据。
使用地址来间接传输数据时,这些数据放在RAM里,对于这块RAM,要保证这几点:
- RAM的所有者、操作者,必须清晰明了。
-
- 这块内存,就被称为"共享内存"。要确保不能同时修改RAM。比如,在写队列之前只能由发送者修改这块RAM,在读队列之后只能由接收者访问这块RAM。
- RAM要保持可用
-
- 这块RAM应该是全局变量,或者是动态分配的内存。对于动然分配的内存,要确保它不能提前释放:要等到接收者用完后再释放。另外,不能是局部变量。
如上图代码,创建一个队列,长度为1,元素大小是一个char*
类型指针变量,再创建一个写队列任务,优先级是1,再创建一个读队列任务,优先级是2。
如上图,发送任务中,将指向字符串指针的地址(二级指针)写入到队列中,读取任务中,从队列的二级指针中将字符串地址拷贝到buffer
中。
- 队列中存放的并不是整个字符串,而是指向字符串的指针。
如上图,读取任务成功从队列中拿到了字符串所在的地址,通过该指针找到了字符串并且打印出来。
这个程序故意设置接收任务的优先级更高,在它访问数组的过程中,发送任务无法执行、无法写这个数组,从而保证这个数组中数据的安全,使得接收任务读取到的肯定是正确的值,不会发生“读取到一半被切换下去,让写队列任务向队列中写数据”的情况。
邮箱:
FreeRTOS的邮箱概念跟别的RTOS不一样,这里的邮箱称为"橱窗"也许更恰当:
- 它是一个队列,队列长度只有1。
- 写邮箱:新数据覆盖旧数据,在任务中使用
xQueueOverwrite()
,既然是覆盖,那么无论邮箱中是否有数据,该函数总能成功写入数据。 - 读邮箱:读数据时,数据不会被移除;在任务中使用
xQueuePeek()
,这意味着,第一次调用时会因为无数据而阻塞,一旦曾经写入数据,以后读邮箱时总能成功。
如上图代码,main函数就不贴图了,创建了写队列任务,优先级是2,创建了读队列任务,优先级是1。在写队列任务中,先延时5个Tick
,然后再覆盖式的向队列中写入数据。
在读队列任务中,使用xQueuePeek
窥视队列中的数据,不延时,偷看成功则打印数据,不成功则打印不能接收数据的字符串。
如上图所示,写队列任务先开始执行,一上来就延时进入阻塞状态,此时队列中还没有数据,所以此时正在运行的读队列任务无法从队列中读取数据,所以打印无法接收数据的字符串。
5个Tick
之后,写队列任务被唤醒后抢占读队列任务,向队列中写入数据,然后再次进入延时,此时队列中已经有数据了,所以读取队列的任务可以从队列中偷看到数据,由于写数据任务处于阻塞状态,所以在一段时间内都没有覆盖数据,所以读到的数据是相同的。
5个Tick
之后,写队列任务又被唤醒,用新数据覆盖队列,然后再进入延时阻塞状态,读队列任务读取数据,如此反复。
- 除了第一次,读队列任务每次都能偷看到数据,无论是否写入新的数据。
🍉总结
要理解同步和互斥的概念,认识到同步和互斥是相互依赖的。要明白队列是如果实现同步和互斥的,大概清楚队列的运行机制。除此之外还要掌握不同情况下队列的使用。
相关文章:

【RTOS学习】同步与互斥 | 队列
🐱作者:一只大喵咪1201 🐱专栏:《RTOS学习》 🔥格言:你只管努力,剩下的交给时间! 同步与互斥 | 队列 🍉同步与互斥🍦同步🍦互斥 🍉队…...
Python订单生成器+队列+异步提高性能和容错
以下代码实现了一个订单生成器,使用 asyncio 和 aioredis 库实现了高并发地生成订单,并将新增订单异步更新到数据库。具体实现流程如下: 初始化 OrderGenerator 类。传入 Redis 服务器地址和并发数,在初始化函数中设置并发数和一…...
理德名人故事:全球投资之父-约翰.邓普顿
说到约翰‧邓普顿,我们就会想到他的很多标签。比如全球投资之父、史上最成功的基金经理等等。他是邓普顿集团的创始人,一直被誉为全球最具智慧以及最受尊崇的投资者之一。福布斯资本家杂志称他为"全球投资之父"及"历史上最成功的基金经理…...

微前端三:qiankun 协作开发和上线部署
我们先看qiankun怎么上线部署: 我这边用的是yaml 文件在 rancher上部署的: base是基座,这里每个应用都是一个服务,这个还是跟之前一样并没有区别,那如何在一个域名上挂载多个服务呢? 最开始我们主要是在in…...

HTML三叉戟,标签、元素、属性各个的意义是什么?
🌟🌟🌟 专栏详解 🎉 🎉 🎉 欢迎来到前端开发之旅专栏! 不管你是完全小白,还是有一点经验的开发者,在这里你会了解到最简单易懂的语言,与你分享有关前端技术和…...

prometheus获取kubelet接口监控数据
一、前言 k8s集群的kubelet服务内部有自带的cadvisor服务用于收集k8s集群的监控数据,所以可以通过调用kubelet的接口就能获取pod的资源监控数据,在新版本的k8s中,kubelet的监控数据获取端口为10250端口,老版本的是10255端口 二、…...

国产主控应用案例:汉王电子血压计-君正开发板
2023春季新品发布会上汉王科技发布柯氏音法电子血压计产品—汉王电子血压计,继嗅觉检测盒之后再次深度布局大健康领域。 不同于当前市面上使用示波法原理的电子血压计,汉王电子血压计采用血压测量金标准中的柯氏音法,由此引领一场电子血压计领…...

万宾科技智能井盖传感器特点介绍
当谈论城市基础设施的管理和安全时,井盖通常不是第一项引人注目的话题。然而,传统井盖和智能井盖传感器之间的差异已经引起了城市规划者和工程师的广泛关注。这两种技术在功能、管理、安全和成本等多个方面存在着显著的差异。 WITBEE万宾智能井盖传感器E…...
YoloV8改进策略:SwiftFormer,全网首发,独家改进的高效加性注意力用于实时移动视觉应用的模型,重构YoloV8
文章目录 摘要论文:《SwiftFormer:基于Transformer的高效加性注意力用于实时移动视觉应用的模型》1、简介2、相关研究3、方法3.1、注意力模块概述3.2、高效的加性注意力3.3、SwiftFormer 架构4、实验4.1、实现细节4.2、基线比较4.3、图像分类4.4、目标检测和实例分割4.5、语义…...
Jupyter Notebook在指定位置打开
1、在Jupyter Notebook设置文件中修改默认路径 anconda prompt输入: jupyter notebook --generate-config 找到配置文件路径:C:\Users\Lenovo.jupyter 打开文件,修改默认路径: ## The directory to use for notebooks and kernel…...

树控件的使用
目录 1、修改树控件的基础属性: 2、准备图标 : (1)、ico后缀的图片放入当前文件路径的rc中 (2)、在Icon中添加资源,导入图片 (3)、准备HICON图标 (4&am…...
C++实现顺序栈类的定义,编写main ()函数验证顺序栈类设计的合理性
C实现顺序栈类的定义,编写main ()函数验证顺序栈类设计的合理性 以下是一个简单的C代码示例,用于实现顺序栈类的定义并编写main()函数来验证其合理性: #include <iostream> using namespace std;const int MAX_SIZE 100; // 定义栈的…...
手机直播助手软件app哪个好用?
手机直播助手软件现在可谓是多如牛毛,从上半年魔棒手机自动直播软件上线以来。几乎全国所有的科技公司都效仿魔棒手机自动直播软件兴起手机直播助手开发热。相对来说,简单的手机直播助手软件没什么技术门槛。但是手机无人直播助手软件要做精做全则很难。…...

腾讯待办宣布关停,哪款待办事项提醒APP好?
如果你之前一直使用微信中的“腾讯待办”小程序来记录待办事项并设置定时提醒,那么你就会发现腾讯待办在2023年10月16日通过其官方微信公众号、小程序发布了业务关停公告,将于2023年12月20日全面停止运营并下架,并且有导出数据的提示。 腾讯…...

【单片机毕业设计】【hj-006-7】CO、有害混合气体检测 | 空气质量检测 | 有害气体检测
一、基本介绍 项目名: 基于单片机的CO、有害混合气体检测系统设计 基于单片机的空气质量检测系统设计 基于单片机的有害气体检测系统设计 项目编号:mcuclub-hj-006-7 单片机类型:STC89C52 具体功能: 1、通过MQ-7检测CO值&#x…...

wpf主页面解析
1、 开头的网址作用 1和2都是引入命名空间的,每一个字符串代表一系列的命名空间,这样就可以不用一个一个引用了。wpf中规定有一个名称空间是可以不加名字的,xmlns不加名字是默认命名空间。 "http://schemas.microsoft.com/winfx/2006/x…...
三相交错LLC软启动控制程序算法实现---充电桩电源设计实战细节
简介 充电桩充电终端是一款单枪最大功率达到600kW的充电桩。它具有以下特点: 充电枪线长3.5米,重量小于90kg,额定电压1000V,最大电流600A,最大功率600kW。 高宽深为1700340295mm。 该充电桩采用模块化设计,具有较高的可靠性和可维护性。 充电时间大约在30分钟左右,…...

Chrome 115之后的版本,安装和使用chromedriver
在Python中使用selenium 时报如下错误: 1. 老版本chrome对应的chromedriver 下载地址:CNPM Binaries Mirror 2. 新版本chrome对应的chromedriver 下载地址:Chrome for Testing availability...

潮玩宇宙:收藏、交流与竞技的数字乐园
最近爆火的新项目潮玩宇宙,想必有很多人入场了,代理商宣传投资147一个月回本,确实是现在做到了,现在平台一颗宝石的价格已经超过美金了,还有一大部分人在等待进场,潮玩宇宙旗下奖券世界和养猪农场已经做了有…...

企拓客app骗局为不实信息,企拓客保持正常经营状态
网络运营软件盛行,带来了一些混乱的现状,造成企业不同程度的损失,也让运营单位在选择软件时心有余悸。真真假假的信息,扰乱了运营软件的市场环境,热门的拓客软件不慎躺枪。企拓客就是其中之一。 笔者通过网上检索的方式,最终查证企拓客软件官方保持着正常运营,企拓客app骗局不攻…...

Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...