【RTOS学习】信号量 | 互斥量 | 递归锁

news2024/11/18 15:49:49

🐱作者:一只大喵咪1201
🐱专栏:《RTOS学习》
🔥格言:你只管努力,剩下的交给时间!
图

信号量 | 互斥量 | 递归锁

  • 🍺信号量
    • 🥤原理
    • 🥤使用信号量的函数
    • 🥤基本使用
  • 🍺互斥量
    • 🥤原理
    • 🥤使用互斥量的函数
    • 🥤互斥量的基本使用
    • 🥤优先级反转
    • 🥤优先级继承
  • 🍺递归锁
    • 🥤大概原理
    • 🥤使用递归锁的函数
    • 🥤使用
  • 🍺总结

🍺信号量

信号量也是FreeRTOS实现同步与互斥的方式。

图
如上图便是信号量的模型,任务A和任务B是生产者任务,负责生产数据,任务C和任务D是消费者任务,负责消费数据。

生产者每生产一个数据,信号量就加1,当增加到用户设定的限定值时直接失败返回。

消费者每消费一个数据,信号量就减一,当信号量减到0的时候,消费者任务就处于阻塞状态,直到新数据到来才被唤醒。

  • 被唤醒时,谁的优先级高就唤醒谁,优先级相同就唤醒阻塞时间最长的任务。

从信号量这个名字来看:

  • 信号:起通知作用
  • 量:还可以用来表示资源的数量

没有限制时,它就是“计数型信号量”,当只有0、1两个取值时,它就是“二进制信号量”。

  • 二进制信号量和计数型信号量的唯一差别,就是计数值的最大值被限定为1。
  • 支持give给出资源,计数值加一,还支持take获得资源,计数值减一。
  • 信号量本身就是一个共享资源。

计数型信号量的典型场景是:

  • 计数:数据产生时"give"信号量,让计数值加1;处理数据时要先"take"信号量,就是获得信号量,让计数值减1。
  • 资源管理:要想访问资源需要先"take"信号量,让计数值减1;用完资源后"give"信号量,让计数值加1。
  • 信号量只表示数据的存在情况,相当于买票时票的余量,它并不管理数据本身。

🥤原理

图

如上图,在使用信号量的时候需要先调用xSemaphoreCreateCounting来创建信号量,可以看到,它底层调用的是xQueueCreateCountingSemaphore函数,从名字就可以看出其实是创建了一个队列。

在函数内部会调用xQueueGenericCreate创建一个队列,该队列的大小就是指定信号量的计数值。

  • 信号量的上限值就是队列的长度。

图
如上图,使用xSemaphoreGive增加信号量时,其本质就是向队列中写数据,每增加1就写一个数据。

使用xSemaphoreTake减少信号量时,其本质就是从队列中读取数据,每减1就读取一个数据。

由于信号量的底层其实是队列,所以它实现同步与互斥的原理是和队列是类似的。

信号量和队列的对比:

队列信号量
可以容纳多个数据,有两部分内存:队列结构体、存储数据的空间只有计数值,无法容纳其他数据
生产者:没有空间存入数据时可以阻塞生产者:不用阻塞,计数值达到最大时失败返回
消费者:没有数据时可以阻塞消费者:没有资源时可以阻塞
  • 除了使用内存上的区别外,最大的区别就是信号量的生产者任务一般不阻塞,失败了就直接返回。

🥤使用信号量的函数

使用信号量时,先创建、然后去添加资源、获得资源。使用句柄来表示一个信号量。

创建二进制信号量:

/* 动态创建 */
SemaphoreHandle_t xSemaphoreCreateBinary( void );

/* 静态创建 */
SemaphoreHandle_t xSemaphoreCreateBinaryStatic(
				StaticSemaphore_t *pxSemaphoreBuffer );
  • 动态创建时不用传参,只需要接收返回的信号量句柄。
  • 静态创建时,需要用户指定存放信号量的内存空间pxSemaphoreBuffer

创建计数型信号量:

/* 动态创建 */
SemaphoreHandle_t xSemaphoreCreateCounting(
					UBaseType_t uxMaxCount, 
					UBaseType_t uxInitialCount);

/* 静态创建*/					
SemaphoreHandle_t xSemaphoreCreateCountingStatic( 
					UBaseType_t uxMaxCount, 
                    UBaseType_t uxInitialCount, 
                    StaticSemaphore_t *pxSemaphoreBuffer );
  • uxMaxCount:最大计数值
  • uxInitialCount:计数初始值
  • pxSemaphoreBuffer:静态创建时需要指定存放信号量的内存。
  • 返回值:创建成功返回信号量句柄

增加信号量:

BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
  • xSemaphore :要增加的信号量句柄。
  • 返回值:成功返回pdTRUE,失败返回pdFALSE

获取信号量:

BaseType_t xSemaphoreTake(
                   SemaphoreHandle_t xSemaphore,
                   TickType_t xTicksToWait);
  • xSemaphore:要减少的信号量句柄
  • xTicksToWait:无法获得信号量时的阻塞时间,0:不阻塞,马上返回,portMAX_DELAY一直阻塞直到被唤醒。
  • 返回值:成功返回pdTRUE,失败返回pdFALSE

删除信号量:

对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。

void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
  • xSemaphore :要删除的信号量句柄
  • 无返回值,必然会成功。

🥤基本使用

二进制信号量:

通过二进制信号量来解决两个任务同时使用一个串口的时的缺陷:

图
如上图,创建一个二进制信号量,此时信号量的初始值是0,所以需要先使用xSemaphoreGive给它增加1。

当两个任务在使用串口时,先Take信号量,如果Take成功才能使用串口,否则就阻塞等待,使用完毕后需要Give信号量,以便另一个任务使用串口。

图
如上图,两个任务在交替使用串口。

这个过程中,两个任务只能有一个任务使用串口,有任务在使用串口,这个二进制信号量就是0,另一个任务就无法Take,只能阻塞,就实现了互斥的目的。

  • 使用信号量不存在Take到一半时被切换走,因为获取过程中会关闭所有中断,不会调度其他任务。
  • 这种要么不做,要做就做完的性质称为原子性

信号量的TakeGive操作是具有原子性的。

计数型信号量:

tu
如上图,在使用计数型信号量之前,必须先定义宏开关configUSE_COUNTING_SEMAPHORES

图
如上图代码,先创建计数型信号量,计数上限是3,初始值是0,然后创建两个任务,发送任务vSender用来增加信号量,优先级是2,接收任务vReceiver用来减少信号量,优先级是1。

图
如上图代码所示,任务vSender的优先级高,所以先执行,它连续四次增加信号量,增加成功打印成功对应的信息,成功计数值加一,失败的话打印失败信息,失败计数值加一,然后进入延时阻塞状态。

任务vReceiver的此时才有机会运行,它不断减少信号量,减少成功就打印成功信息,成功计数值加一,失败的话就打印失败信息,失败计数值加一,等vSender延时结束后抢占CPU,继续增加信号量,如此反复。

图
如上图所示运行结果,vSender任务连续四次Giver信号量中,只有前三次是成功的,因为信号量的上限是3。

vReceiverTake信号量时,也值能成功三次,因为信号量的计数值只到3,由于它是阻塞式Take,所以没有信号量后就处于阻塞状态了,故而没有答应错误信息。

🍺互斥量

拿最开始(上篇文章)中上厕所的例子来说,怎么独享厕所?你当然可以进去后,让别人帮你把门:但是,命运就掌握在别人手上了。

使用队列、信号量,都可以实现互斥访问,以信号量为例:

  • 信号量初始值为1
  • 任务A想上厕所,"take"信号量成功,它进入厕所
  • 任务B也想上厕所,"take"信号量不成功,等待
  • 任务A用完厕所,"give"信号量;轮到任务B使用

这需要两个前提才能保证安全:

  • 任务B很老实,不撬门(不"give"信号量)。
  • 没有坏人:别的任务不会"give"信号量。

前面信号量的介绍中也可以看到,负责givetake信号量的任务并不是同一个。

所以说,使用信号量确实可以实现互斥访问,但是并不完美,最完美的方式是:自己开门上锁,完事了自己开锁。

使用互斥量就可以解决这个问题:
t图
如上图,互斥量的值只能为1或者0,它也经常被称为,也就是只有两个状态,上锁和解锁。

任务A上锁后,互斥量就变为0,此时其他任何一个任务都无法再申请到这个锁,只有任务A解锁,互斥量变为1后,其他任务才能申请到锁进行上锁。

  • 它的核心在于:谁上锁,就只能由谁开锁。

很奇怪的是,FreeRTOS的互斥锁,并没有在代码上实现这点,甚至是Linux也没有实现:即使任务A获得了互斥锁,任务B竟然也可以释放互斥锁,后面在使用本喵会用代码演示。

  • 谁上锁,谁就解锁,这只是一个约定,需要程序员自己去维护。

在多任务系统中,任务A正在使用某个资源,还没用完的情况下任务B也来使用的话,就可能导致问题。比如对于串口,任务A正使用它来打印,在打印过程中任务B也来打印,客户看到的结果就是A、B的信息混杂在一起。

再比如对于同一个变量,比如 int a ,如果有两个任务同时写它就有可能导致问题:

图

对于变量a的修改,C代码只有一句a = a + 8,但是实际上它分3步实现:读出原数值,修改数值,写回到内存。

我们想让任务A、B都执行add_a函数,函数执行两次,最终的结果是1 + 8 + 8 = 17

现在假设任务A运行完代码①,在执行代码②之前被任务B抢占了:现在任务A的R0等于1。任务B执行完add_a函数,a等于9。

任务A继续运行,在代码②处R0仍然是被抢占前的数值1,执行完②③的代码,a等于9,这跟预期的17不符合。

  • 修改变量、设置结构体、在16位的机器上写32位的变量,这些操作都是非原子的。也就是它们的操作过程都可能被打断,如果被打断的过程有其他任务来操作这些变量,就可能导致冲突。

上述问题的解决方法是:任务A访问全局变量、函数代码时,独占它,就是上个锁。这些全局变量、函数代码必须被独占地使用,它们被称为临界资源

🥤原理

图
如上图,使用互斥量之前需要先调用xSemaphoreCreateMutex创建互斥量,其实是在调用xQueueCreateMutex,它的底层会创建一个队列,长度为1。

从创建互斥量函数的名字中也可以看出,其实就是一个类似二进制型的信号量,底层同样也是用的队列。

所以当锁被任务A申请走以后,任务B就会被阻塞,同样是放在队列的接收阻塞链表中,当任务A归还锁后,任务B才会被唤醒去申请锁。

  • 为了独立理解互斥量,就认为它是一把锁,只有上锁和开锁两种状态,申请锁后就上锁了,归还后就开锁了。

既然是互斥量的本质就是一个二进制型的信号量,那么申请锁和归还锁用的也是信号量的TakeGive方式。


互斥量也被称为互斥锁,使用过程如下:

  • 互斥量初始值为1
  • 任务A想访问临界资源,先获得并占有互斥量,然后开始访问
  • 任务B也想访问临界资源,也要先获得互斥量:被别人占有了,于是阻塞
  • 任务A使用完毕,释放互斥量;任务B被唤醒、得到并占有互斥量,然后开始访问临界资源
  • 任务B使用完毕,释放互斥量

正常来说:在任务A占有互斥量的过程中,任务B、任务C等等,都无法释放互斥量。但是FreeRTOS未实现这点:任务A占有互斥量的情况下,任务B也可释放互斥量,后面本喵会演示。

🥤使用互斥量的函数

创建互斥量:

/* 动态创建 */
SemaphoreHandle_t xSemaphoreCreateMutex( void );
/* 静态创建 */
SemaphoreHandle_t xSemaphoreCreateMutexStatic( 
				StaticSemaphore_t *pxMutexBuffer );
  • pxMutexBuffer :静态创建时需要用户指定存放互斥量的内存空间。
  • 返回值:用于控制互斥量的句柄。

申请锁/释放锁:

/* 申请锁 */
BaseType_t xSemaphoreTake(
                   SemaphoreHandle_t xSemaphore,
                   TickType_t xTicksToWait );
/*释放锁*/
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
  • xSemaphore:互斥量的句柄。
  • xTicksToWait :等待时间
  • 返回值:成功返回pdTRUE,失败返回pdFALSE
  • 互斥量本质上就是信号量,所以互斥量的句柄类型也是SemaphoreHandle_t

删除互斥量:

void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

🥤互斥量的基本使用

tu
如上图,使用互斥锁之前,需要先定义宏configUSE_MUTEXS

一个任务上锁,另一个任务解锁:

图
如上图,创建一个互斥量,再创建两个任务,申请锁任务优先级是2,释放锁任务优先级是1,优先级为2的任务先执行。

图
如上图,Take任务先执行,先申请互斥锁,成功则打印成功信息后阻塞,失败则直接阻塞,让GiveAndTake任务有机会执行。

GiveAndTake任务执行时,先尝试申请一下互斥锁,成功则打印成功信息,不成功则打印失败信息,并且将互斥锁私自释放,释放成功则打印相关信息,然后再申请互斥锁,成功则打印成功信息后阻塞,失败后直接阻塞。

  • GiveAndTake尝试申请互斥锁,如果不成功则监守自盗。

图
如上图,Take申请互斥锁成功后,GiveAndTake任务是无法再次申请到这个互斥锁的,进而私自释放互斥锁,然后再申请互斥锁,从而监守自盗成功。

  • 切记切记,我们写代码时不要这么做。

两个任务使用同一个串口:

仍然使用两个任务使用串口的例子,虽然用二进制信号量解决了,但是最恰当的还是信号量:
图
如上图,创建互斥量后,不用像二进制信号量那样要Give一下进行初始化,互斥量创建函数中内部已经进行了初始化。

图
如上图,在使用串口前需要先上锁,使用完毕后再解锁,当任务1上锁了以后,任务2是无法使用串口的。

  • 上锁和解锁之间的代码就是临界区,上锁能够保证临界区的安全,让所有任务串行执行临界区代码。

图
如上图,此时任务1和任务2在交替使用串口,比如不会出现打印信息的混乱。

🥤优先级反转

互斥锁虽然没有实现谁上锁谁解锁,但是它实现了优先级继承,用来解决优先级反转的问题。

图
如上图,此时有三个任务,任务A的优先级是1,任务B的优先级是2,任务C的优先级是3,它们共同使用一把互斥锁。

任务B和任务C先阻塞一会,让任务A先运行,任务A在运行的过程中申请了互斥锁,当任务B和任务C延时结束以后,最高优先级的任务C开始运行。

任务C在运行的过程中也要申请这把锁,但是锁已经被任务A申请走了,任务A此时已经被切换走了进入阻塞状态,它抱着锁走了。所以任务C无法申请到锁也进入阻塞状态。

此时能运行的就只有任务B了,因为任务B的优先级高于任务A,所以任务A始终得不到运行,也就始终无法释放锁。

  • 这就导致,优先级最高的任务C因为无法申请到锁而阻塞无法运行,优先级发生了反转。

图

如上图代码,创建三个任务,分别执行低优先级任务vLPTask,中优先级任务vMPTask,高优先级任务vHPTask,也就是任务A,任务B和任务C。

  • 这里必须创建二进制信号量,不能创建互斥锁,因为二进制信号量没有解决优先级反转的功能,才能看到优先级反转的实验现象。

图
如上图,低优先级任务中,将对应的标志位置一,然后申请互斥锁,之后进行长时间的运算,运算完毕后归还锁。

图
二上图,中优先级任务先延时,防止抢占低优先级任务,让低优先级任务先运行。

图
如上图代码,高优先级任务一开始也进入延时,防止抢占低优先级任务,延时结束后申请互斥锁,然后再释放互斥锁。

图
如上图所示运行结果,低优先级任务先执行,成功申请互斥锁,但是还没有来得及归还,高优先级任务抢占执行,低优先级任务抱着锁被切走了。

高优先级任务运行后也申请锁,但是申请失败,所以阻塞了,此时中优先级任务得到了机会始终运行。

  • 这种现象就是优先级反转。

🥤优先级继承

而互斥锁的成功解决了优先级反转这一问题,它是通过优先级继承解决的。

图
如上图,前面部分和优先级反转一样,任务C因为无法申请到锁而进入阻塞状态。

但是使用互斥锁时,短暂地将任务C的高优先级3赋给任务A,此时任务A的优先级就比任务B的优先级高,所以此时执行的是任务A而不是B。

直到任务A将锁归还以后,立刻将任务A的高优先级回收,任务A的优先级又变成了1,此时任务C的优先级最高,所以任务C执行。

由于任务A已经将锁归还了,所以任务C可以顺利的申请到锁而继续执行下去,此时就符合最高优先及的任务抢占执行的特性了。

  • 优先级继承就是,短暂提高低优先级任务到高优先级,让它有机会执行,归还互斥锁,然后再恢复到低优先级。

图
如上图代码,只需要将原本创建的二进制型信号量变成创建互斥量,就可以解决优先级反转的问题,其他代码都不用作任何改变。

图
如上图运行结果,和我们分析的一致,当高优先级任务因为低优先级任务抱着锁被切走而无法申请锁时,短暂的赋予低优先级任务高优先级的权利。

原本的低优先级任务得以执行,释放互斥锁后恢复到了原本的低优先级,此时高优先级任务抢占执行,并且申请锁成功,不再阻塞,从而始终执行。

在优先级继承这个过程中,中优先级任务就没有机会被执行,此时就完美解决了优先级反转的问题。

🍺递归锁

假设这样的场景: 任务 A 获得了互斥锁 M,它又调用了一个函数,这个函数函数也要去获取同一个互斥锁 M,于是它阻塞,此时任务 A 休眠,等待任务 A来释放互斥锁!

  • 任务A自己阻塞不动了,还在等着自己释放互斥锁来救自己。
  • 此时就产生了死锁

就像我们找工作的时候,公司只招有工作经验的人,但是我们没有工作经验,又只能去找工作。

图
如上图代码所示,创建一个互斥锁,再创建一个任务,在该任务中申请锁,申请成功后打印一句话表示自己在运行,然后调用另一个函数,在这个函数中也申请这个锁,申请成功也打印一句话,表示该函数在执行,没有申请成功就阻塞。

图
如上图,只有新创建的任务在申请锁成功够打印了一句话,它调用的另一个函数并没有打印,说明这个函数申请锁失败了。

但是程序并没有运行下去,此时整个任务处于阻塞状态,因为在两处申请了同一把锁,而且第一次申请完后还没有来得及释放,这就是发生了死锁

  • 普通的互斥锁,在FreeRTOS中,发生死锁后,可以由其他任务来释放锁,但是不建议这么做,因为它设计的初衷就是谁申请,谁释放。

这样来说,上面这种情况就不能有吗?如果就是需要这种场景呢?此时就可以使用递归锁(Recursive Mutexes)

  • 任务A获得递归锁M后,还可以多次去获得这个锁。
  • Take了N次,要GiveN次,这锁才会释放。
  • 递归锁实现了由谁上锁就必须由谁解锁,其他人不能解锁。

🥤大概原理

图
如上图,递归锁的结构大概如上图所示,它虽然也是一个互斥量,但是和互斥量不同的是,它还有一个专门用来记录任务TCB节点的成员,以及一个记录申请锁次数的计数值。

Task1申请到递归锁以后,TCB*成员中存放的就是Task1,此时Task2再来申请这个递归锁时,由于和记录的TCB节点不符,就会拒绝Task2申请递归锁。

对于Task1,它可以多次申请递归锁,每Take申请一次,递归锁内部的计数值就会加一,每Give释放一次,该计数值就会减一。

所以Task1申请了多少次就必须释放多少次,否则这个锁就一直属于Task1,其他任务无法申请。

🥤使用递归锁的函数

递归锁的函数和普通互斥锁的函数名不一样,但是参数类型一样,所以本喵就不介绍它的参数类型了:

功能递归锁普通互斥锁
创建xSemaphoreCreateRecursiveMutexxSemaphoreCreateMutex
申请锁xSemaphoreTakeRecursivexSemaphoreTake
释放锁xSemaphoreGiveRecursivexSemaphoreGive

可以看到,递归锁的常用操作也是只有这三个,在函数名上比普通互斥锁多了Recursive表示这是递归锁的函数。

🥤使用

图
如上图,在使用递归锁之前,先要定义互斥锁的宏开关configUSE_RECURSIVE_MUTEXES

一个任务申请递归锁另一个释放:

图
如上图,创建两个任务,任务1的优先级是2,任务2的优先级1,开始调度后让任务1先执行。

图
如上图,任务1先开始运行,连续多次申请递归锁,每申请成功一次就打印一次成功信息,失败则打印失败信息,当多次申请完毕后,让自己进入阻塞状态,让出CPU让任务2执行。

任务2开始执行后,先释放任务1申请的递归锁,如果释放成功则打印成功信息,失败则打印失败信息,之后再申请任务1的递归锁,如果申请成功则打印成功信息,失败打印失败信息。

图
如上图,任务1五次申请递归锁都成功,此时意味着递归锁中的计数值就成了5。任务2 释放 任务1申请的递归锁失败了,之后 申请 任务1的递归锁也失败了。

  • 谁申请的递归锁,必须由谁释放。
  • 递归锁只能由一个任务申请。

解决死锁问题:

图
如上图,仍然是前面产生死锁的例子,只是这里将普通互斥锁换成了递归锁,将申请锁和释放锁的方式换成对应递归锁的方式,其他没有变。

图
如上图,此时就不再有死锁的情况了,两个函数都可以执行,OtherFunctionTaskFunction申请递归锁还没有释放的情况下又再次申请成功。

🍺总结

信号量是基于队列构建的,只负责管理数据的余量,不管理数据本身,互斥量是基于二进制型信号量构建的,它的计数值只有0和1,并且增加了一个优先级继承功能来解决优先级反转的问题。又衍生出了递归锁解决了死锁问题和监守自盗的问题。

这三种结构,最底层还是队列,所以它们实现同步与互斥的原理和队列是相同的。

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

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

相关文章

Java反射获取内部类方法

Java反射获取内部类方法 结论一、案例准备二、测试方法:使用反射获取类的成员内部类和方法具体操作具体操作(使用getDeclaredClasses) 结论 Java 通过反射可以获得内部类,包括内部类属性信息和方法。 一、案例准备 创建了一个类…

1024,向着“顶尖程序员“迈进

10月24日,对每个程序员而言,都是一个具有特殊意义的日子。1024这个数字,不再只是计算机存储容量的基础单位,更是我们向着技术巅峰进发的象征。 回顾我的程序员之路,那是一个不断学习、不断成长的过程。起初是对编程充…

『第二章』这只燕子很特别:Swift 特性

在本篇博文中,您将学到如下内容: 1. Swift 语言概览2. Objective-C “练废了”,重新写一门新语言吧!3. Swift 的“习性”与优势3.1. Swift 更简洁、更易于阅读、所需代码更少3.2. Swift 更加安全3.3. Swift 内存管理更加统一3.4. Swift 更快3.5. Swift 会…

Redis主从模式(二)---拓扑结构及复制过程

目录 一, Redis主从模式下的复制拓扑结构 1.1 一主一从结构 1.2 一主多从结构 1.3 树形主从结构 二, 主从复制过程 2.1 主从复制建立复制流程图 2.2 数据同步(psyc) 1.replicationid/replid (复制id) 2.offset(偏移量) 2.3 psync运行流程 2.4 全量复制 2.5 部分复制…

Opencv-图像插值与LUT查找表

图像像素的比较 白色是255,黑色是0 min(InputArray src1,InputArray src2,OutputArray dst) max(InputArray src1,InputArray src2,OutpurArray dstsrc1:第一个图像矩阵,通道数任意src2:第二个图像矩阵,尺寸和通道数以及数据类型…

【C++面向对象】5. this指针

文章目录 【 1. 基本原理 】【 2. 实例 】 【 1. 基本原理 】 在 C 中,只有成员函数才有 this 指针(友元函数没有 this 指针,因为友元不是类的成员),this 指针是所有成员函数的隐含参数。 在成员函数内部,…

用*画田字形状,numpy和字符串格式化都可以胜任

numpy的字符型元素矩阵,可以方便画;直接python字符串手撕,也可以轻巧完成。 (本笔记适合熟悉循环和列表的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网:https://www.python.org/ Free:大咖免费“圣经”教程《…

【WinForm详细教程一】WinForm中的窗体、Label、TextBox及Button控件、RadioButton和CheckBox、ListBox

文章目录 1.WinForm文件结构2. 窗体的常用属性、方法与事件2.1 常用属性(可直接在属性中设置)2.2 常用方法2.3 常用事件 3.Label、TextBox及Button控件4.RadioButton和CheckBox5.ListBox(列表框) 1.WinForm文件结构 .sln文件 &am…

IEEE754 标准存储浮点数

1. IEEE754 标准简介 IEEE754 标准是一种用于浮点数表示和运算的标准,由国际电工委员会(IEEE)制定。它定义了浮点数的编码格式、舍入规则以及基本的算术运算规则,旨在提供一种可移植性和一致性的方式来表示和处理浮点数 IEEE754 …

浅谈电力电容器的故障处理及选型

安科瑞 华楠 【摘要】常见的电力电容器都是为了改善电力系统的电压质量和提高输电线路的输电能力,它们在减少系统功率损耗、提高功率因数、降低运行电流、提升电网电压、释放变压器使用裕度等方面有着显著效果。按电压等级可以划分高、低压两部分。虽然它们可以起着…

Vue mixin混入

可以把多个组件中共有的配置提取出来构成一个混入。 一、配置混入 (一) 创建mixin.js 这里的名字可以自定义,但是为了方便识别,多数场景下都写mixin。 mixin.js 要创建在src目录下,与main.js平级: &…

win10启动venv报错:无法加载文件 venv\Scripts\activate.ps1,因为在此系统上禁止运行脚本。

背景: 最近需要用到python开发,切换虚拟环境时,在win10系统上安装编辑器后创建了虚拟环境,但是执行activate时报错:.\venv\Scripts\activate,报错内容如题: 无法加载文件 venv\Scripts\activa…

HTTPSConnectionPool(host=‘huggingface.co‘, port=443)解决

huggingface,也就是抱抱脸,应该都很熟悉了吧 好用是很好用,就是有一个问题,国内的IP地址总是不灵是吧 今天我就碰到这么个问题 请看图: 。。。。图找不到了,我的问题忘记记录了 我给你们贴上文字吧 (M…

reactNative导入excel文件

组件内导入 import {TouchableOpacity,PermissionsAndroid} from react-native; import RNFS from react-native-fs; import XLSX from xlsx; import DocumentPicker from react-native-document-picker; import {Buffer} from buffer;// 需要安装一下三个,Buffer和react-nati…

Standford Compiler Course Assignment 2

第二部分的作业是语法分析,通过编写cool.y(这个assignment的任务),利用bison将其自动生成语法分析LALR(1)的代码。 语法分析,就是将词法分析阶段已经识别好的token,按照语法的规则,构建抽象语法树的过程。 比如以下的…

读书笔记之《敏捷测试从零开始》(一)

大家好,我是rainbowzhou。 子曰:学而时习之,不亦说乎?今天我想和大家分享一本测试书籍——《敏捷测试从零开始》。以下为我的读书笔记: 精彩片段摘录: 焦虑往往来自于对比,当你在自己的圈子里面…

Elasticsearch之mapping

文章目录 以显式的方式创建一个映射查看某个具体索引的mapping定义向已存在的映射中添加一个新的属性查看映射中指定字段的定义信息更新已存在映射的某个字段 1、 官方文档地址 2、 字段类型 1、定义:映射是定义文档及其包含的字段如何存储和索引的过程。 2、每个…

LabVIEW在 XY Graph中选择一组点

LabVIEW在 XY Graph中选择一组点 问题:有一个包含许多点的XY Graph,在程序开发中,对于显示XY Graph中的多个点,如何进行选取。最好能像图像处理中的ROI一样,并且它们的颜色可以更改,可以在其中选择一些ROI…

企业安全—SDL概述篇

0x00 前言 众所周知,从源头开始就开发安全的代码,比产品已经成型之后付出的代价要小很多,也就是一直在说的安全左移的概念。最好就是从一开始,大家就用最安全的代码,或者是框架,那么开发出来的产品必然会减…

C++ string 类的其他操作

4.3.2 string 类的其他操作 在C新增string类之前,程序员也需要完成诸如给字符串赋值等工作。对于C语言式的字符串,程 序员使用C语言库中的函数来完成这些任务。头文件cstring(以前为string.h)提供了这些函数。例如,可 以使用函数 strcpy()将字符串复制到字符数组中,使用函数…