freeRTOS学习(四)

news2024/11/23 16:22:07

队列管理

队列提供了任务到任务、任务到中断和中断到任务的通信机制。

队列的特征

数据存储
队列可以保存有限数量的固定大小的数据项。一个队列所能容纳的最大条目数称为它的长度。每个数据项的长度和大小都在创建队列时设置。

队列通常用作先进先出(FIFO)缓冲区,其中数据被写入队列的末端,并从队列的头部删除。
图31展示了向用作FIFO的队列写入和读取数据的过程。
在这里插入图片描述
在这里插入图片描述

  1. 创建一个队列。允许任务A和任务B通信。队列中最多可以容纳五个整数。当队列被创建时,它不包含任何值,因此为空。
  2. 任务A将一个局部变量的值写入到队列的后面。由于队列之前是空的,所以写入的值现在是队列中唯一的项。因此它既是队列后的值也是队列前的值。
  3. 任务A改变本地变量的值,并再次写入队列。队列现在包含两个值。第一个写入的值保持在队列前面,新值插入到队列的末尾。
  4. 任务B从队列头读取一个值。
  5. 任务B删除了一个项,只留下任务A写入的第二个值留在队列中。

有两种方式可以实现队列行为:

  1. 复制队列:将发送到队列的数据逐个字节地复制到队列中。
  2. 引用队列:队列只保存指向发送给队列的数据的指针,而不是数据本身。

**FreeRTOS使用复制队列的方法,**通过复制队列比引用队列更强大和更简单,因为:

  • 栈变量可以直接发送到队列中,即使在声明它的函数退出后该变量不存在。
  • 可以直接将数据发送到队列,而不需要首先分配一个缓冲区来保存数据,然后将数据复制到已分配的缓冲区中。
  • 发送任务可以立即重用发送到队列的变量或缓冲区。
  • 发送任务和接收任务是完全分离的——程序设计人员不用关心哪个任务拥有数据,或者哪个任务发布数据。
  • 通过复制队列并不阻止队列被引用。例如,当队列中的数据的大小使得将数据复制到队列中不现实时,可以将指向数据的指针复制到队列中。
  • RTOS完全负责分配用于存储数据的内存。
  • 在受内存保护的系统中,任务可以访问的RAM将受到限制。在这种情况下,只有当发送和接收任务都可以访问存储数据的RAM时,才可以使用引用队列。复制队列不存在这种限制:内核总是以完全特权运行,允许使用队列跨内存保护边界传递数据。

多任务访问
队列本身就是对象,任何知道它们存在的任务或ISR都可以访问它们。任意数量的任务都可以写同一个队列,也可以从同一个队列读任意数量的任务。

队列读取阻塞
当任务试图从队列中读取时,它可以选择指定一个“阻塞”时间。如果队列已经为空,则该任务保持在Blocked状态,以等待从队列中获得数据。
处于阻塞状态的任务正在等待队列中的数据可用,当另一个任务或中断将数据放入队列中时,该任务会自动移动到Ready状态。如果指定的块时间在数据可用之前过期,任务也会自动从Blocked状态移动到Ready状态。
队列可以有多个读取者,因此单个队列上可能阻塞多个任务等待数据。在这种情况下,当数据可用时,只有一个任务被解除阻塞。未阻塞的任务将始终是等待数据的最高优先级任务。如果阻塞的任务具有相同的优先级,那么等待数据时间最长的任务被解除阻塞。

队列写阻塞
与从队列读取时一样,任务也可以在写入队列时指定阻塞时间。在这种情况下,阻塞时间是在队列已经满的情况下,任务保持在Blocked状态来等待队列上有可用空间的最长时间。
队列可以有多个写入者,因此一个完整的队列上可能阻塞了多个任务,等待完成发送操作。
在这种情况下,当队列上的空间变为可用时,只有一个任务将被解除阻塞。未阻塞的任务将始终是等待空间的最高优先级任务。如果阻塞的任务具有相同的优先级,那么等待空间最长的任务将被解除阻塞。

多队列阻塞
可以将队列分组到集合中,允许任务进入Blocked状态以等待数据在集合中的任何队列上变为可用。

使用队列

**xQueueCreate()**API 函数:在使用队列之前,必须显示地创建队列。创建一个队列并返回引用它所创建的队列的QueueHandle_t。
队列由句柄引用,句柄是QueueHandle_t类型的变量。

当创建队列时,FreeRTOS从FreeRTOS堆中分配RAM。RAM用于保存队列数据结构和队列中包含的项。如果要创建的队列没有足够的堆RAM可用,xQueueCreate()返回NULL。

QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,UBaseType_t uxItemSize);
  • uxQueueLength :正在创建的队列在任何时候可以容纳的最大项数。
  • uxItemSize:每个数据项大小。
  • Return Value:如果返回NULL,则不能创建队列,因为FreeRTOS没有足够的堆内存来分配队列数据结构和存储区域。返回的非NULL值作为已经创建队列的句柄。

在创建队列之后,可以使用xQueueReset()API函数将队列返回到原始的空状态。

xQueueSendToBack()和xQueueSendToFront()

  • xQueueSendToBack():将数据发送到队列的尾部。等价于xQueueSend()。
  • xQueueSendToFront():将数据发送到队列的头部。

注意:永远不要从中断服务程序中调用xQueueSendToFront()或xQueueSendToBack()。在中断服务程序中应该使用xQueueSendToFrontFromISR()和xQueueSendToBackFromISR()

BaseType_t xQueueSendToFront(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);
  • xQueue:数据被写入的队列的句柄。队列句柄从从用于创建队列的xQueueCreate()调用中返回。
  • pvItemToQueue:指向要复制到队列中的数据的指针。队列可以保存的每个项的大小是在创建队列时设置的,因此这些字节将从pvItemToQueue复制到队列存储区域。
  • xTicksToWait:如果队列已满,任务保持在Blocked状态等待队列上有可用空间的最长时间。
    如果xTicksToWait为零且队列已满,xQueueSendToFront()和xQueueSendToBack()将立即返回。
    阻塞时间以Tick周期指定,因此它表示绝对时间依赖于Tick频率。宏pdMS_TO_TICKS()可用于将毫秒转换为指定的ticks。
    如果在FreeRTOSConfig.h中将INCLUDE_vTackSuspend设置为1,那么将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待。
  • Returned value:1.pdPASS:当数据成功发送到队列时,才会返回pdPASS。如果指定了块时间(xTicksToWait不为零),那么在函数返回之前,调用任务可能被置于Blocked状态以等待队列中有可用的空间,但在块时间过期之前,数据被成功写入队列。2.errQUEUE_FULL:队列已满,无法将数据写入队列。。如果指定了块时间(xTicksToWait不为零),那么调用任务将被置于Blocked状态,等待另一个任务或中断在队列中腾出空间,但指定的块时间在此发生之前过期了。

**xQueueReceive()**API函数:从队列中读取数据项,然后从队列中删除此项目。
注意:永远不要从中断服务例程调用xQueueReceive()。要使用xQueueReceiveFromISR()

BaseType_t xQueueReceive(QueueHandle_t xQueue,void * const pvBuffer, TickType_t xTicksToWait);
  • xQueue:读取数据的队列句柄。队列句柄有xQueueCreate()返回。
  • pvBuffer:一个指针,指向接收到的数据被复制到其中的内存。队列所包含的每个数据项的大小必须在创建队列时设置。pvBuffer所指向的内存必须足够大,能够容纳这么多字节。
  • xTicksToWait:如果队列已经为空,任务应该保持Blocked状态,等待队列上的数据变为可用的最长时间。如果xTicksToWait为零,且队列已经为空,则函数立即返回。
    阻塞时间以Tick周期指定,因此它表示的绝对时间依赖于tick频率,宏pdMS_TO_TICKS()将毫秒转换为yicks。
    如果在FreeRTOSConfig.h中将INCLUDE_vTaskSuspend设置为1,再将xTicksToWait设置为portMAX_DELAY,将导致任务无限期等待。
  • Returned value :1.pdPASS:只有当数据成功从队列中读取时,才会返回pdPASS。若指定了块时间(xTicksToWait不为零),那么调用任务可能被置于Blocked状态,等待队列上数据可用,但在块时间过期之前成功地读取到了数据。2.errQUEUE_EMPTY:如果由于队列已经为空,无法从队列中读取数据,则返回errQUEUE_EMPTY。如果指定了阻塞时间(xTicksToWait不为零),那么调用任务将被置于阻塞状态,以等待另一个任务或中断向队列发送数据,但阻塞时间在此之前已经过期。

**uxQueueMessagesWaiting()**API函数:查询当前在队列中的项的数量。
永远不要从中断服务例程调用uxQueueMessagesWaiting()。应该使用uxQueueMessagesWaitingFromISR()。

UBaseType_t uxQueueMessagesWaiting(QueueHandle_t xQueue);
  • xQueue:正在查询队列的句柄。
  • Returned value:正在查询的队列当前持有的项数。如果返回零,则队列为空。

例10.当从队列接收数据时阻塞

  • 这个例子演示了创建一个队列,多个任务向队列发数据,以及从队列接收数据。
  • 创建该队列保存类型为int32_t的数据项。
  • 发送数据到队列的任务不指定阻塞时间,而从队列接收数据指定阻塞时间。
  • 发送数据到队列的任务的优先级低于从队列读取数据的任务的优先级。
  • 这意味着队列不应该包含超过一个项,因为一旦数据被发送到队列,接收任务将解除阻塞,抢占发送任务,并删除数据——队列再次为空。
  • 创建两个写入队列的任务,一个持续将值100写入队列,另一个持续将值200写入同一队列。task参数用于将这些值传递给每个任务实例。
static void vSendrTask(void *pvParameters)
{
	int32_t lValueToSend;
	BaseType_t xSatus;
	/*创建了该任务的两个实例,因此发送到队列的值通过task参数传入—这样,每个实例可以使用不同的值。创建队列是为了保存类型为int32_t的值,因此将参数强制转换为所需的类型。*/
	lValueToSend = (int32_t)pvParameters;
	/*和大多数任务一样,这个任务是在一个无限循环中实现的。*/
	for(;;)
	{
		/*将值发送到队列。

第一个参数是数据被发送到的队列。队列是在启动调度器之前创建的,因此是在此任务开始执行之前。

第二个参数是要发送的数据的地址,在本例中是lValueToSend的地址。

第三个参数是阻塞时间——在队列已经满的情况下,任务应该保持阻塞状态以等待队列上有可用空间的时间。在这种情况下,没有指定块时间,因为队列永远不应该包含一个以上的项,因此永远不会满。*/
		xStatus = xQueueSendToBack(xQueue,&lValueToSend,0);
		if(xStatus != pdPASS)
		{
			/*发送操作无法完成,因为队列已满-这一定是一个错误,因为队列不应该包含超过一个项!* /
			vPrintString( "Could not send to the queue.\r\n" ); 
		}
	}
}

接收任务指定了100毫秒的阻塞时间,因此将进入阻塞状态等待数据可用。当队列上有数据可用或者超过100毫秒时,它会离开Blocked状态。100毫秒的超时应该永远不会过期,因为有两个任务不断地向队列写入。

static void vReceiverTask(void *pvParameters)
{
	int32_t lReceivedValue;
	BaseType_t xStatus;
	const TickType_t xTicksToWait = pdMS_TO_TICKS(100);
	for(;;)
	{
		/*该调用应该总是发现队列为空,因为该任务将立即删除写入队列的任何数据。*/
		if(uxQueueMessagesWaiting(xQueue) != 0)
		{
			vPrintString( "Queue should have been empty!\r\n" ); 
		}
		/*从队列接收数据。

第一个参数是接收数据的队列。队列是在调度程序启动之前创建的,因此是在此任务第一次运行之前。

第二个参数是缓冲区,接收到的数据将放在其中。在这种情况下,缓冲区只是一个变量的地址,该变量具有容纳接收到的数据所需的大小。最后一个参数是块时间——如果队列已经为空,任务将保持在Blocked状态以等待数据可用的最长时间。*/
		xStatus = xQueueReceive(xQueue,&lReceivedValue,xTicksToWait);
		if(xStatus == pdPASS)
		{
			/*从队列中成功接收数据,打印接收的值。*/
			 vPrintStringAndNumber( "Received = ", lReceivedValue ); 
		}
		else{
			/*队列等待100ms后仍未收到数据。

这一定是一个错误,因为发送任务是自由运行的,将不断写入队列。*/
			 vPrintString( "Could not receive from the queue.\r\n" ); 
		}
	}
}

在启动调度器之前创建队列和三个任务,创建该队列以保存最多5个int32_t值。

/*声明一个QueueHandle_t类型的变量,用于存储三个任务都要访问的队列的句柄。*/
int main()
{
	/*创建队列以保存最多5个值,每个值都足够容纳一个类型为int32_t的变量*/
	xQueue = xQueueCreate(5,sizeof(int32_t));
	if (xQueue != NULL)
	{
		/*创建两个将发送到队列的任务实例。task参数用于传递任务将写入队列的值,因此一个任务将持续向队列写入100,而另一个任务将持续向队列写入200。两个任务都在优先级1上创建。*/
		xTaskCreate(vSenderTask,"Sender 1",1000, (void *)100, 1,NULL);
		xTaskCreate(vSenderTask,"Sender 2",1000, (void *)200, 1,NULL);
		/*创建从队列中读取的任务。该任务的优先级为2,因此高于发送方任务的优先级。*/
		xTaskCreate(vReceiverTask,"Receiver",1000,NULL,2,NULL);
		vTaskStartScheduler();
	}
	else
	{
		/*无法创建队列。*/
	}
	/*如果一切正常,main()将永远不会到达这里,因为调度器现在正在运行任务。如果main()到达这里,那么很可能没有足够的FreeRTOS堆内存用于创建空闲任务。第2章提供了关于堆内存管理的更多信息。*/
	for(;;);
}

发送到队列的两个任务具有相同的优先级。这将导致两个发送任务依次向队列发送数据。示例10产生的输出如图32所示。
在这里插入图片描述
在这里插入图片描述

  1. Receiver任务首先运行,因为它具有最高优先级。它尝试从队列中读取。队列为空,因此接收器进入阻塞状态,等待数据可用。Sender 2在Receiver阻塞后运行。
  2. Sender 2写入队列,导致Receiver退出阻塞状态,因为Receiver拥有最高优先级,因此优先于Sender 2。
  3. Receiver清空队列,再次进入阻塞状态。这一次,Sender 1在阻塞后运行。
  4. Sender 1写入队列,导致Receiver退出Blocked状态并抢占Sender 1—因此它继续…

从多个来源接收数据

在FreeRTOS设计中,任务从多个源接收数据是很常见的。接收任务需要知道数据来自哪里,来确定该如何处理数据。
一个简单的设计方案是使用队列来传输结构,结构的字段中包含数据的值和数据的源。
在这里插入图片描述

  • 创建一个包含Data_t类型结构的队列。结构成员允许数据值和枚举类型,来指示在一条消息中发送到队列的数据意味着什么。
typedef struct{
	ID_t eDataID;
	int32_t lDataValue;
}Data_t;
  • 中央控制器任务用于执行主要的系统功能。它必须对队列上与之通信的系统状态的输入和更改做出反应。
  • CAN总线任务用于封装CAN总线接口功能。当CAN总线任务接收并解码一条消息时,它将把已解码的消息以Data_t结构发送给Controller任务。传输结构的eDataID成员用于让Controller任务知道数据是什么——在所描述的情况下,它是一个电机转速值。被传输结构的IdaraValue成员让Controller任务知道实际的电机转速值。
  • Human Machine Interface(HMI)任务用于封装所有HMI功能。机器操作员可能可以以121种方式输入命令和查询值,这些都必须在HMI任务中检测和解释。当一个新命令被输入时,HMI任务以Data_t结构将该命令发送给Controller任务。传输结构的eDataID成员用于让Controller知道数据是什么。IDataValue成员用于让Controller任务知道实际的设定值。

例11.发送消息到队列时阻塞,并在队列上发送有结构的消息

  • 接收任务的优先级低于发送任务。
  • 队列用于传递结构而不是整数。
/*用于标识数据来源的枚举类型。*/
typedef enum
{
	eSender1,
	eSender2
}DataSource_t;
/*定义将传递给队列的结构类型。*/
typedef struct{
	uint8_t ucValue;
	DataSource_t eDataSource;
}Data_t;
/*声明两个Data_t类型的变量,它们将在队列上传递。*/
static const Data_t xStructsToSend[2] = 
{
	{100,eSender1},
	{200,eSender2}
}
  • 发送任务具有更高的优先级,所以此时队列通常是满的。因为一旦接收任务从队列中删除一个项,它会被发送任务中的一个抢占,然后重新填充队列。发送任务重新进入Blcoked状态,等待队列上再次有可用的空间。
  • 发送任务指定了100毫秒的阻塞时间,因此它进入阻塞状态,以便在每次队列满时等待可用空间。
  • 当队列上有可用空间,或者过了100毫秒没有可用空间时,它会离开Blocked状态。在本例中,100毫秒的超时永远不会过期,因为接收任务不断地通过队列中删除项来创造空间。
static void vSenderTask(void *pvParameters)
{
	BaseType_t xStatus;
	const TickType_t xTicksToWait = pdMS_TO_TICKS(100);

	for(;;)
	{
		/*发送消息到队列
		第二个参数是被发送的结构的地址,地址作为任务参数传入,因此直接使用pvParameters。第三个参数是阻塞时间——如果队列已经满了,任务应该保持阻塞状态以等待队列上有可用空间的时间。之所以指定阻塞时间,是因为发送任务的优先级高于接收任务,因此队列预计将被填满。当两个发送任务都处于Blocked状态时,接收任务将从队列中删除项。*/
		xStatus = xQueueSendToBack(xQueue,pvParameters,xTicksToWait);
		if(xStatus != PASS)
		{
			/*发送操作无法完成,即使在等待100ms后。

这一定是一个错误,因为只要两个发送任务都处于Blocked状态,接收任务就应该在队列中腾出空间。*/
			 vPrintString( "Could not send to the queue.\r\n" );
		}
	}
}
  • 接收任务的优先级最低,因此只有当两个发送任务都处于Blocked状态时,它才会运行。发送任务只有在队列满时才会进入Blocked状态,因此接收任务也只能在队列满时执行。
static void vReceiverTask(void *pvParameters)
{
	/*声明保存从队列接收到的值的结构*/
	Data_t xReceivedStructure;
	BaseType_t xstatus;
	for(;;)
	{
		if(uxQueueMessagesWaiting(xQueue) != 3)
		{
			 vPrintString( "Queue should have been full!\r\n" ); 
		}
		/*第二个参数是缓冲区地址*/
		xStatus = xQueueReceive(xQueue,&xReceivedStructure,0);
		if(xStatus == pdPASS)
		{
			/*从队列中成功接收数据,打印出接收的值和值的来源。*/
			if(xReceivedStructure.eDataScource == eSender1){
			 vPrintStringAndNumber( "From Sender 1 = ", xReceivedStructure.ucValue ); 
			}
			else{
				vPrintStringAndNumber( "From Sender 2 = ", xReceivedStructure.ucValue );
			}
		}
		else{
			/*没有从队列中收到任何东西。这一定是一个错误,因为该任务应该只在队列已满时运行。*/
			 vPrintString( "Could not receive from the queue.\r\n" ); 
		}
	}
}
int main(void)
{
	xQueue = xQueueCreate(3,sizeof(Data_t));
	if(xQueue != NULL)
	{
		xTaskCreate(vSenderTask,"sender1",1000,&(xStrcutsToSend[0]),2,NULL);
		xTaskCreate(vSenderTask,"sender2",1000,&(xStrcutsToSend[1]),2,NULL);

		 xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL ); 
		  vTaskStartScheduler();
	}
	else{
	}
	for(;;);
}

处理大型或可变大小的数据

如果存储在队列中的数据的大小很大,那么最好使用队列来传输指向数据的指针,而不是一个字节一个字节地将数据复制到队列中。传输指针在处理时间和创建队列所需的RAM量方面都更有效。
当使用指针队列时,必须特别小心,以确保:

  1. 被指向的RAM的所有者是明确定义的。
    当通过指针在任务之间共享内存时,必须确保两个任务不会同时修改内存内容,或采取任何可能导致内存内容无效或不一致的其它操作。
  2. 被指向的RAM仍然有效。
    如果所指向的内存是动态分配的,或者是从预分配的缓冲池中获得的,那么应该只有一个任务负责释放内存。在释放内存之后,任何任务都不应该尝试访问它。

如何使用队列将指向缓冲区的指针从一个任务发送到另一个任务。

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

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

相关文章

【利用AI让知识体系化】常见的移动端适配知识

I. 引言 A. 移动设备的普及度 移动设备的普及度近年来持续攀升,据统计,截至2021年,全球手机用户数量已达51.98亿,而智能手机的普及率则已经超过了70%,成为人们生活中最为重要和常用的工具之一。 同时,平…

chatgpt赋能python:Python如何更改?

Python如何更改? 如果您想成为一名成功的Python程序员,那么您需要知道如何更改Python代码。在这篇文章中,我们将介绍Python如何更改,并提供一些实用的技巧和建议来使您的编码更加高效和有用。 什么是Python? Python…

chatgpt赋能python:Python如何填充颜色

Python如何填充颜色 Python是一种简单易学但功能丰富的编程语言,被广泛用于各种开发领域。其中填充颜色是Python中的一个非常重要的功能,在很多项目中都会经常用到。本文将介绍Python如何填充颜色,让你快速上手。 什么是填充颜色 填充颜色…

Python matplotlib库的使用

目录 画图的两种基本方式: 隐藏边框: 隐藏坐标系 设置网格线 共享坐标轴 双坐标轴 设置坐标轴标签及刻度字体大小 设置坐标轴标签据离坐标轴距离 画点与线 标注文字 画不同大小的多个坐标系 调节子图间距 导入库: import matplotl…

【JavaSE】Java(五十):核心要点

文章目录 1. JDK 和 JRE 有什么区别2. \和 equals 的区别3. final 在java中有什么作用4. Java中的Math() 类有哪些常用方法5. String 属于基础数据类型吗? 1. JDK 和 JRE 有什么区别 JDK(Java Development Kit)和 JRE(Java Runtime Environ…

FusionCharts Suite XT 3.20.X Crack

3.20版# 2023年3月24日 新功能 FusionCharts 3.20版本引入了一种新方法_changeXAxisCordinates,它允许用户自动更改x轴,使其在图例或数据交互时居中对齐。 FusionCharts 3.20版本更新了Angular集成,支持Angular版本14和15。 FusionChart…

HTML (Hyper Text Markup Language)

目录 网页(html文件) 什么是HTML? web标准 为啥需要web标准 web标准的构成 VScode的使用 HTML标签 基本语法 标签关系 结构标签 课间拓展: 了解骨架代码 HTML中常见的标签 标题标签 注释标签 段落标签 换行标签 文本格式化标签 div 和span 标签 图像标签 …

【vulnhub靶场】MATRIX-BREAKOUT: 2 MORPHEUS

文章目录 描述:一、开启靶机信息收集二层发现三层探测信息整理:初步攻击basic爆破:已知漏洞利用文件上传 后渗透测试后渗透测试 描述: 这是《黑客帝国》系列的第二部,副标题是《沉睡魔咒:1》。它的主题是回到第一部《…

chatgpt赋能python:Python如何在图片上添加文字

Python如何在图片上添加文字 对于网站的SEO优化而言,图片上的文字也是非常重要的一环。而Python是一种常用的编程语言,可以通过一些Python库来在图片上添加文字。 PIL库介绍 PIL(Python Imaging Library)是Python中常用的图像处…

面对日益增加的网络安全风险,需要全面的API安全

全球商业界在过去几年中面临的挑战是前所未有的。流行病、通货膨胀、能源危机、战争、经济衰退以及供应链的碎片化和延误都给组织带来了问题,没有一个行业、市场或地区未受影响。 然而,尽管存在这些问题,我们的数字生态系统和足迹变得越来…

chatgpt赋能python:Python如何更改主题

Python 如何更改主题 Python 是一种非常强大的编程语言,能够适用于多种领域,包括数据分析、机器学习、Web 开发等。Python 社区为开发者提供了各种主题,这篇文章将介绍 Python 如何更改主题。 什么是主题? 主题是指编程环境的外…

Web服务器开发、文件上传

1 Stream的读写操作 2 http模块web服务 3 request请求对象 4 response响应对象 5 axios node中使用 6 文件上传的细节分析 前面一篇提到的内容是node对底层的文本操作,还没有涉及到从文本文件获取信息然后传递给客户端和如何响应客户端请求。这里开始了解如何实…

chatgpt赋能python:Python实现人脸识别的可能性和局限性

Python实现人脸识别的可能性和局限性 随着计算机视觉技术的不断发展,人脸识别技术也得到了广泛应用,其中Python作为一种高效、简洁和易于学习的编程语言,被广泛用于人脸识别算法的开发和实现。 什么是人脸识别? 人脸识别是一种…

chatgpt赋能python:Python如何导入CSV的完全指南

Python如何导入CSV的完全指南 CSV是一种常见的数据格式,在数据分析和处理中使用广泛。使用Python,我们可以轻松地读取、处理和分析CSV文件。在本指南中,我们将介绍如何使用Python导入CSV文件。 什么是CSV文件? CSV文件是按照逗…

chatgpt赋能python:Python如何将内容写进文件:从初学者到高级编程工程师的终极指南

Python如何将内容写进文件:从初学者到高级编程工程师的终极指南 Python是一种流行的编程语言,具有简洁而易于阅读的语法,开发人员可以非常方便地使用它来读取和写入文件。 本文将从基础到高级介绍使用Python将内容写入文件。 基础知识 在P…

论文笔记--Evaluating Large Language Models Trained on Code

论文笔记--Evaluating Large Language Models Trained on Code 1. 文章简介2. 文章概括3 文章重点技术3.1 评估3.2 模型训练--Codex3.3 微调模型--Codex-S3.4 微调模型--Codex-D 4. 文章亮点5. 原文传送门6. References 1. 文章简介 标题:Evaluating Large Languag…

chatgpt赋能python:Python构造函数详解

Python构造函数详解 在Python中,构造函数是一种特殊的函数,用于创建类的实例并初始化其属性。Python构造函数的名称为__init__,它在创建类的实例时自动调用。本篇文章将全面介绍Python构造函数的重要性及其使用方法。 为什么需要构造函数&a…

Python之并发编程协程

一、介绍 基于单线程来实现并发,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发,为此我们需要先回顾下并发的本质:切换保存状态 CPU正在运行一个任务,会在两种情况下切走去执行其他的任务&a…

从零手写操作系统之RVOS抢占式多任务实现-06

从零手写操作系统之RVOS抢占式多任务实现-06 多任务系统的分类抢占式多任务的设计代码任务切换流程分析系统启动任务mepc初始化首个被调度执行的任务任务切换 兼容协作式多任务软件中断编码实现 测试 注意点 本系列参考: 学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春 整理而…

QT QVerticalSpacer 弹簧控件

本文详细的介绍了QVerticalSpacer控件的各种操作,例如:新建界面、控件布局、隐藏控件、设置宽高、添加布局、其它参数、.h源文件、cpp源文件、其它文章等等操作。 实际开发中,一个界面上可能包含十几个控件,手动调整它们的位置既费…