FreeRTOS:4.内存管理

FreeRTOS内存管理

目录

  • FreeRTOS内存管理
    • 1. 为什么不直接使用C库函数的malloc和free函数
    • 2. FreeRTOS的五种内存管理方式
    • 3. heap4源码分析
      • 3.1 堆内存池
      • 3.2 内存块的链表数据结构
      • 3.3 堆的初始化
      • 3.4 堆的内存分配
      • 3.5 堆的内存释放
    • 4. 总结

1. 为什么不直接使用C库函数的malloc和free函数

在C语言的库函数中,有malloc、free等函数,但是在FreeRTOS中,它们不适用:

  • 不适合用在资源紧缺的嵌入式系统中
  • 这些函数的实现过于复杂、占据的代码空间太大
  • 并非线程安全的(thread-safe)
  • 运行有不确定性:每次调用这些函数时花费的时间可能都不相同
  • 内存碎片化
  • 使用不同的编译器时,需要进行复杂的配置
  • 有时候难以调试

2. FreeRTOS的五种内存管理方式

FreeRTOS中内存管理的接口函数为:pvPortMalloc、vPortFree,对应于C库的malloc、free。

在这里插入图片描述

  • heap_1:只分配,不回收;只实现了pvPortMalloc,没有实现vPortFree,因此不会产生碎片问题,分配时间确定
  • heap_2:采用最佳匹配算法,会产生大量内存碎片,没有对内存碎片进行合并,分配时间不定
  • heap_3:采用标准C库的malloc、free,由于并非线程安全,因此heap_3中先暂停rtos调度器,再进行分配,会产生内存碎片,时间不定
  • heap_4:是目前常用的堆管理方式,采用首次适应算法,能够合并相邻内存块,减少内存碎片,同样分配时间不定
  • heap_5:在heap_4的基础上,它可以管理多块、分隔开的内存。

3. heap4源码分析

目前heap_4的使用最为广泛,本文基于heap_4.c的源码进行进一步分析。

源码路径:Middlewares\Third_Party\FreeRTOS\Source\portable\MemMang\heap_4.c``

3.1 堆内存池

/* Allocate the memory for the heap. */
#if( configAPPLICATION_ALLOCATED_HEAP == 1 )
	/* The application writer has already defined the array used for the RTOS
	heap - probably so it can be placed in a special segment or address. */
	extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#else
	static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */

heap4采用的是线性数组结构,大小为configTOTAL_HEAP_SIZE个字节

如果定义了configAPPLICATION_ALLOCATED_HEAP宏,可以由用户层自行定义内存池的位置

3.2 内存块的链表数据结构

/* Define the linked list structure.  This is used to link free blocks in order
of their memory address. */
typedef struct A_BLOCK_LINK
{
	struct A_BLOCK_LINK *pxNextFreeBlock;	/*<< The next free block in the list. */
	size_t xBlockSize;						/*<< The size of the free block. */
} BlockLink_t;

通过BlockLink_t这个链表对空闲的内存块进行管理,这个结构体包括内存块的大小以及指向下一个内存块头的指针,并且由两个静态链表指针xStart、pxEnd来标识开头和结尾。除了xStart以外,其余内存控制块都是在内存池中,用指针的形式进行访问。也就是说,xStart是作为哨兵节点,xStart的pxNextFreeBlock指针就是第一个空闲块节点。

static BlockLink_t xStart, *pxEnd = NULL;

另外,heapMINIMUM_BLOCK_SIZE限制了内存块的最小值,当内存块小于heapMINIMUM_BLOCK_SIZE时,就不再维护这个内存碎片了。

3.3 堆的初始化

通过prvHeapInit函数完成堆的初始化

static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
size_t uxAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;

	/* Ensure the heap starts on a correctly aligned boundary. */
	uxAddress = ( size_t ) ucHeap;
	// 内存对齐
	if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
	{
		uxAddress += ( portBYTE_ALIGNMENT - 1 );
		uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
		xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;
	}

	pucAlignedHeap = ( uint8_t * ) uxAddress;
	// 初始化xStart
	/* xStart is used to hold a pointer to the first item in the list of free
	blocks.  The void cast is used to prevent compiler warnings. */
	xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
	xStart.xBlockSize = ( size_t ) 0;

	/* pxEnd is used to mark the end of the list of free blocks and is inserted
	at the end of the heap space. */
    // 初始化pxEnd
	uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
	uxAddress -= xHeapStructSize;
	uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
	pxEnd = ( void * ) uxAddress;
	pxEnd->xBlockSize = 0;
	pxEnd->pxNextFreeBlock = NULL;

	/* To start with there is a single free block that is sized to take up the
	entire heap space, minus the space taken by pxEnd. */
	pxFirstFreeBlock = ( void * ) pucAlignedHeap;
	pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
	pxFirstFreeBlock->pxNextFreeBlock = pxEnd;

	/* Only one block exists - and it covers the entire usable heap space. */
	xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
	xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;

	/* Work out the position of the top bit in a size_t variable. */
	xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}

该函数主要完成了内存块链表的初始化。初始化完成后,xStart.pxNextFreeBlock指向了内存池对齐后的首地址,pxEnd指针则指向了内存池末尾往前的xHeapStructSize个字节大小位置。

因此,除了内存对齐后减少的空间外,内存池末尾还留有xHeapStructSize个字节存放pxEnd指向的内存头,用于标识末尾。

/* The size of the structure placed at the beginning of each allocated memory
block must by correctly byte aligned. */
static const size_t xHeapStructSize	= ( sizeof( BlockLink_t ) + ( ( size_t ) ( portBYTE_ALIGNMENT - 1 ) ) ) & ~( ( size_t ) portBYTE_ALIGNMENT_MASK );

#define portBYTE_ALIGNMENT			8
#define portBYTE_ALIGNMENT_MASK ( 0x0007 )

在stm32中,xHeapStructSize为8字节。

3.4 堆的内存分配

通过pvPortMalloc函数完成堆的内存分配

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;

	vTaskSuspendAll(); // 进入临界区
	{
		/* 如果是第一次调用pvPortMalloc,那么就调用prvHeapInit来初始化堆内存池 */
		if( pxEnd == NULL )
		{
			prvHeapInit();
		}

		// 判断申请的内存大小是否超过上限
		if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
		{
			/* The wanted size is increased so it can contain a BlockLink_t
			structure in addition to the requested amount of bytes. */
			if( xWantedSize > 0 )
			{
				xWantedSize += xHeapStructSize; // 申请的内存要加上额外的内存头8字节

				/* Ensure that blocks are always aligned to the required number
				of bytes. */
				if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
				{
					/* 内存对齐 */
					xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
					
				}

			}
			// 想要申请的内存比剩余的空闲内存小
			if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
			{
				/* 遍历各个空闲内存块,找到第一个空闲内存块,它的大小比想要申请的内存大 */
				pxPreviousBlock = &xStart;
				pxBlock = xStart.pxNextFreeBlock;
				while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
				{
					pxPreviousBlock = pxBlock;
					pxBlock = pxBlock->pxNextFreeBlock;
				}

				/* If the end marker was reached then a block of adequate size
				was	not found. */
				if( pxBlock != pxEnd )
				{
					/* Return the memory space pointed to - jumping over the
					BlockLink_t structure at its start. */
					pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );

					/* 把 pxBlock 从空闲链表中移除 */
					pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;

					/* 判断产生的内存碎片是否大于规定的最小内存碎片 */
					if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
					{
						/* 创建一个新的空闲内存块 */
						pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );

						/* 计算两个内存块的大小 */
						pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
						pxBlock->xBlockSize = xWantedSize;

						/* 将新的内存块插入到空闲链表中 */
						prvInsertBlockIntoFreeList( pxNewBlockLink );
					}
						/* 更新剩余的空闲内存 */
					xFreeBytesRemaining -= pxBlock->xBlockSize;

					if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
					{
						xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
					}

					/* The block is being returned - it is allocated and owned
					by the application and has no "next" block. */
					pxBlock->xBlockSize |= xBlockAllocatedBit;
					pxBlock->pxNextFreeBlock = NULL;
				}
			}
		}

		traceMALLOC( pvReturn, xWantedSize );
	}
	( void ) xTaskResumeAll(); // 退出临界区
	// 如果定义了分配内存的钩子函数并且分配失败,就去调用
	#if( configUSE_MALLOC_FAILED_HOOK == 1 )
	{
		if( pvReturn == NULL )
		{
			extern void vApplicationMallocFailedHook( void );
			vApplicationMallocFailedHook();
		}
	}
	#endif

	return pvReturn; // 返回分配的内存块(跳过了内存头部的)
}
/*-----------------------------------------------------------*/
  1. 当首次调用pvPortMalloc,会调用prvHeapInit对内存链表进行初始化
  2. 判断申请的内存大小是否超过上限
  3. 遍历空闲内存块链表,找到第一个内存块大小是足够分配的
  4. 将该内存块从链表中剔除,如果剩余空间大小超过了最小内存块大小,那么就创建新的内存块,并插回内存块链表
  5. 如果分配失败,并且用户注册了对应的回调函数,就去调用

注意到:动态内存对于内存的消耗是大于应用层申请的内存大小,原因在于内存对齐和内存块头部的开销导致。

堆的内存分配和内存释放,都涉及到将内存块插回空闲链表中,heap4_c实现了空闲内存可合并,一定程度上解决内存碎片问题,就在prvInsertBlockIntoFreeList函数中体现。

prvInsertBlockIntoFreeList函数如下:

static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )
{
	BlockLink_t *pxIterator;
	uint8_t *puc;

	/* 找到一个空闲块,这个块的地址比插入的块地址低,下一个块的地址比插入的块地址高 */
	for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
	{
		/* Nothing to do here, just iterate to the right position. */
	}

	/* 找到插入位置后, 判断这个位置的尾地址能否和插入块衔接起来,如果可以就合并 */
	puc = ( uint8_t * ) pxIterator;
	if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
	{
		pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
		pxBlockToInsert = pxIterator;
	}

	/* 判断插入块能否和 插入位置的下一个内存块衔接起来,如果可以就合并 */
	puc = ( uint8_t * ) pxBlockToInsert;
	if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
	{
		if( pxIterator->pxNextFreeBlock != pxEnd )
		{
			/* Form one big block from the two blocks. */
			pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
			pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
		}
		else
		{
			pxBlockToInsert->pxNextFreeBlock = pxEnd;
		}
	}
	else
	{
		pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
	}

	/* If the block being inserted plugged a gab, so was merged with the block
	before and the block after, then it's pxNextFreeBlock pointer will have
	already been set, and should not be set here as that would make it point
	to itself. */
	if( pxIterator != pxBlockToInsert )
	{
		pxIterator->pxNextFreeBlock = pxBlockToInsert;
	}

}
  1. 首先在空闲内存链表中,找到插入位置,即这个块的地址比插入块地址低,这个块的下一个块地址比插入块地址高
  2. 判断插入块能否和左边的空闲内存块以及右边的空闲内存块合并,也就是边界相等,如果可以就合并成一个大的内存块

所以说:heap4_c所谓的空闲内存可合并,只是合并相邻的内存碎片,这是基于内存池的线性连续而设计的。

因此,为了尽可能的减少内存碎片,提升内存合并的作用,尽可能把上电后不释放的动态内存在初始化阶段申请(比如说动态分配的任务,包括TCB和任务栈),然后对于重复申请释放的动态内存,在初始化阶段结束后再分配和使用。也就是说,堆的前半部分内存都用于不释放的动态内存,然后后半部分就用来一些频繁申请释放的动态内存。

3.5 堆的内存释放

通过vPortFree函数完成堆的内存释放

vPortFree函数如下:

void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;

	if( pv != NULL )
	{
		/* The memory being freed will have an BlockLink_t structure immediately
		before it. */
		puc -= xHeapStructSize;

		/* This casting is to keep the compiler from issuing warnings. */
		pxLink = ( void * ) puc;

		/* Check the block is actually allocated. */
		configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
		configASSERT( pxLink->pxNextFreeBlock == NULL );

		if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
		{
			if( pxLink->pxNextFreeBlock == NULL )
			{
				/* The block is being returned to the heap - it is no longer
				allocated. */
				pxLink->xBlockSize &= ~xBlockAllocatedBit;

				vTaskSuspendAll();
				{
					/* Add this block to the list of free blocks. */
					xFreeBytesRemaining += pxLink->xBlockSize;
					traceFREE( pv, pxLink->xBlockSize );
					prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
				}
				( void ) xTaskResumeAll();
			}
		}

	}
}
/*-----------------------------------------------------------*/

释放内存比较简单,就是把申请释放的内存地址前移xHeapStructSize找到内存头部的位置,

然后进入临界区,把这个内存块插入到空闲链表当中,并增大xFreeBytesRemaining变量(记录当前空闲内存大小的变量)的大小,最后退出临界区。

4. 总结

  • heap_4的堆内存池是建立在一片线性连续的内存上的(全局数组)。
  • 在申请和释放内存时,有查询空闲块链表的操作,其最坏的时间复杂度是On,时间是不确定的。
  • 另外由于需要内存头部来维护空闲链表以及内存对齐,这导致了实际可分配的动态内存小于分配的这个线性连续的内存。
  • heap4只能合并相邻的内存碎片,并不能彻底解决内存碎片问题。

参考学习:

freeRTOS动态内存heap4源码分析_freertos heap4-CSDN博客

【FreeRTOS】FreeRTOS内存管理的五种方式-CSDN博客

FreeRTOS 内存管理策略_freertos查看内存碎片功能-CSDN博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LIMS(实验室)信息管理系统源码、有哪些应用领域?采用C# ASP.NET dotnet 3.5 开发的一套实验室信息系统源码

LIMS&#xff08;实验室&#xff09;信息管理系统源码、有哪些应用领域&#xff1f;采用C# ASP.NET dotnet 3.5 开发的一套实验室信息系统源码 LIMS实验室信息管理系统&#xff0c;是一种基于计算机硬件和数据库技术&#xff0c;集多个功能模块为一体的信息管理系统。该系统主…

利用钉钉机器人和PHP开发一款免费的网站可用性检测工具,单节点版

前言 手里有几套系统正在运维&#xff0c;需要保障正常运行&#xff0c;所以可用性检测就必不可少啦&#xff0c; 以前本来是用的阿里官方的云监控&#xff0c;但现在价格感觉太贵了&#xff0c;不划算 那就自己手搓一个简易版的监控吧。 成品效果展示 代码展示 <?php …

2024年哪4种编程语言最值得学习?看JetBrains报告

六个月前,编程工具界的大牛JetBrains发布了他们的全球开发者年度报告。 小吾从这份报告中挑出了关于全球程序员过去一年使用编程语言的情况和未来的采纳趋势,总结出2024年最值得学习的四种编程语言。一起来看看吧。 JetBrains在2023年中开始,就向全球的编程达人们发出了问卷…

海豚调度异常处理: 使用 arthas 在内存中删除启动失败的工作流

&#x1f4a1; 本系列文章是 DolphinScheduler 由浅入深的教程&#xff0c;涵盖搭建、二开迭代、核心原理解读、运维和管理等一系列内容。适用于想对 DolphinScheduler了解或想要加深理解的读者。祝开卷有益。大数据学习指南 大家好&#xff0c;我是小陶&#xff0c;DolphinSch…

笔记本硬盘对拷:升级硬盘的好方法!

如今电子产品更新换代的速度不断加快&#xff0c;笔记本电脑的配置也日新月异。几年前购买的笔记本硬盘容量350G曾经令你感到相当满意。但时至今日&#xff0c;这样的容量是否已经显得有些落后&#xff1f;如果你想要升级硬盘&#xff0c;笔记本硬盘对拷是一个很好的选择。 需要…

工业园区的弱电智能化总体建设规划

在当今迅速发展的工业环境中&#xff0c;一个高效、智能的工业园区是企业成功的重要基石。随着技术的进步&#xff0c;弱电系统的智能化已不仅仅是便利的象征&#xff0c;更是安全生产和效率提升的必要条件。今天&#xff0c;我们将探讨如何通过弱电智能化系统的总体建设规划来…

建筑八大员证报名一寸彩色照片要求及手机自拍方法解读

在建筑行业&#xff0c;八大员证的持有者是广受尊重的专业人士。然而&#xff0c;要成为一名合格的八大员&#xff0c;首先必须通过资格审核和报名流程。其中重要的一步就是提交一寸彩色照片&#xff0c;以确保个人信息准确无误。那么&#xff0c;你是否清楚报名时照片的要求以…

Stable Diffusion 【AI绘画提示词】摄影效果提示词,超美摄影效果摄影特效!让平凡的照片焕发出独特的魅力!

高端的摄影作品需要的专业设备价格昂贵&#xff0c;并不是一般人能够承受的起的&#xff0c;优质摄影作品对光线等一系列要求也非常的高&#xff0c;而AI摄影就完美的解决了这些问题&#xff0c;只需要配合适当的提示词&#xff0c;这些问题都可以迎刃而解。 AI绘画没灵感&…

2024最新最全【Linux常用指令】从零基础入门到精通,看完这一篇就够了

一些能在手机上运行的渗透工具&#xff0c;如下所示&#xff1a; 1. AndroRAT&#xff1a;一款Android远程管理工具&#xff0c;可用于攻击和控制Android设备。 2. DroidSheep&#xff1a;一款用于截取Android设备上所有网络流量的工具。 3. zANTI&#xff1a;一款用于测试网…

给你一个扫码支付的二维码,如何写测试用例?

前言 面试的时候&#xff0c;经常会临场出题&#xff1a;给你一个xxx, 如何测试, 或者说如何写测试用例&#xff1f;xxx可以是圆珠笔&#xff0c;水杯&#xff0c;电梯等生活中常见的场景。 那么给你一个支付的二维码&#xff0c;如何写测试用例呢&#xff1f; 二维码扫码支…

LDR6020显示器应用:革新连接体验,引领未来显示技术

一、引言 随着科技的飞速发展&#xff0c;显示器作为信息展示的重要载体&#xff0c;其性能和应用场景不断得到拓展。特别是在办公、娱乐以及物联网等领域&#xff0c;用户对显示器的需求越来越多样化。在这一背景下&#xff0c;LDR6020显示器的出现&#xff0c;以其卓越的性能…

[图解]《分析模式》漫谈04-Martin Fowler叫的是哪家的士

1 00:00:01,230 --> 00:00:04,190 今天我们来探讨一个有趣的话题 2 00:00:05,130 --> 00:00:08,350 Martin Fowler&#xff0c;他叫的是哪一家的的士 3 00:00:11,980 --> 00:00:15,240 第2章这里&#xff0c;Martin Fowler写 4 00:00:15,250 --> 00:00:18,550 他…

推荐一款mac截图利器

一、介绍 Longshot 是 macOS 上一款功能丰富的截图工具&#xff0c;它提供了多种截图方式和便捷的标注功能。主要包含以下功能特点&#xff1a; 多种截图方式&#xff1a;Longshot 支持区域截图、全屏截图、窗口截图以及滚动截图。 标注工具&#xff1a;提供了丰富的标注工具…

关闭kylin(麒麟)系统的安全认证(烦人的安全认证)

打开grub sudo vim /etc/default/grup修改安全认证选项 增加12行&#xff0c;把13行注释掉 保存更改, 然后执行下面的命令&#xff1a; sudo sync sudo reboot重启成功后&#xff0c;就关闭了安全认证了~~~~~。 总体来讲&#xff0c;kylin还是基于ubuntu的内核的&#xff0c;…

后端常见问题解答-位运算实际场景讲解

位运算 在计算机存储的世界中&#xff0c;一切都是二进制的&#xff0c;位运算就是对二进制位进行操作的一种运算。位运算是计算机中的一种常见运算&#xff0c;可以用来提高性能和提升代码的可读性。 位运算有很多种&#xff0c;比如与、或、非、异或等&#xff0c;这些运算…

NVIDIA Triton系列01-应用概论

NVIDIA Triton系列01-应用概论 推理识别是人工智能最重要的落地应用&#xff0c;其他与深度学习相关的数据收集、标注、模型训练等工作&#xff0c;都是为了得到更好的最终推理性能与效果。 几乎每一种深度学习框架都能执行个别的推理工作&#xff0c;包括 Tensorflow、Pytorc…

el-table 多选回显,分页回显

实现el-table多选分页回显功能&#xff0c;左侧是分页的数据源&#xff0c;右侧是选择后的人员数据&#xff0c;切换下一页&#xff0c;选中的数据会在左侧表格回显。 实现&#xff1a; <template><el-dialog :title"title" :visible.sync"show"…

【Android面试八股文】你知道如何实现非阻塞式生产者消费者模式吗?

文章目录 这道题想考察什么 ?考察的知识点日常生活中的生产者消费者模式生产者消费者模式简介为什么需要缓冲区?阻塞与非堵塞非阻塞式生产者消费者模式的实现非阻塞式生产者消费者模式的实现阻塞式生产者消费者模式实现特点这道题想考察什么 ? 是否了解非阻塞式生产者消费者…

186.二叉树:二叉搜索树中的插入操作(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* Tre…

php实现一个简单的MySQL分页

一、案例演示&#xff1a; 二、php 代码 <?php $servername "localhost"; // MySQL服务器名称或IP地址 $username "root"; // MySQL用户名 $password "123456"; // MySQL密码 $dbname "test"; // 要连接…