06-基础篇-缓存与内存泄露讨论

摄像头若每秒采集25帧,也就是平均40ms采集一帧,我们将摄像头数据通过网络发送给客户端时,会遇到一个问题就是网络是不稳定的,特别是外网状态下。为了不让网络传输的不稳定性影响到采集过程,我们可以用一个线程专门每40ms采集一帧,而另外一个线程专门对采集的数据进行发送。

两个线程的速率并不是一样的,为了让两个不同任务的线程均工作最合理,我们在两个线程之前加入了缓存的概念,也就是生产者 采集线程采集帧后,将数据暂存到缓存中,而消费者 网络传输线程从缓存中取数据并消费。这种缓存过程就是对内存的应用过程。

首先讨论C语言的内存分配函数

c语言 malloc函数开辟内存。

比如

int *p;

p=(int *)malloc (100*sizeof(int));

它开辟100个int单元,即400字节。

通过malloc分配内存后,就可以对内存块进行使用,当使用完内存后,必须采用free函数对内存进行释放。

if(NULL != p)

free(p)

对使用的内存不进行释放操作,就会产生内存泄露。

下面描述在数据缓存中,最常用的两种缓存方式。

1.队列

#include<stdio.h>
#include<malloc.h>

typedef struct Node //定义节点
{
	int data;
	Node *PNEXT;
}NODE,*PNODE;

typedef struct Quene //定义队列
{
	PNODE First;
	PNODE Last;
}QUENE,*PQUENE;

//初始化
void init(PQUENE pQ)
{
	pQ->First = (PNODE)malloc(sizeof(NODE));
	pQ->Last = pQ->First;
	pQ->Last->PNEXT = NULL;
}

//进队
void EnQuene(PQUENE pQ,int val)
{
	PNODE newnode = (PNODE)malloc(sizeof(NODE));
	newnode->data = val;
	pQ->Last->PNEXT = newnode;
	pQ->Last = newnode;
	newnode->PNEXT = NULL;
}

//判断队列是否为空
int ifEmpty(PQUENE pQ)
{
	if(pQ->First == pQ->Last)
	return 1;
	else
	return 0;
}

//出队
int DeQuene(PQUENE pQ)
{
	if(ifEmpty(pQ))
	{
		printf("队列已空,出队失败");
		return 0;
	}
	else
	{
		PNODE p;
		p = pQ->First->PNEXT;
		pQ->First->PNEXT = p->PNEXT;
		int a = p->data;
		free(p);
		return a;
	}
}

//队列数据遍历
void traverse(PQUENE pQ)
{
	PNODE p = pQ->First->PNEXT;
	printf("当前队列内容:");
	while(p->PNEXT != NULL)
	{
		printf("%d ",p->data);
		p = p->PNEXT;
	}
	printf("%d ",p->data);
	printf("\n");
}

//销毁队列
void destory(PQUENE pQ)
{
	while(pQ->First != pQ->Last)
	{
		PNODE p = pQ->First;
		pQ->First = p->PNEXT;
		free(p);
	}

	free(pQ->First);
}

//主程序
int main()
{
	QUENE quene;
	init(&quene);
	EnQuene(&quene,1);
	printf("入队1\n");
	traverse(&quene);
	EnQuene(&quene,2);
	printf("入队2\n");
	traverse(&quene);
	EnQuene(&quene,3);
	printf("入队3\n");
	traverse(&quene);	

	int a = DeQuene(&quene);
	printf("%d值已出列\n",a);
	traverse(&quene);
	destory(&quene);
	return 0;
}

输出结果

入队1
当前队列内容:1 
入队2
当前队列内容:1 2 
入队3
当前队列内容:1 2 3 
1值已出列
当前队列内容:2 3 

2.环形缓存

环形缓冲区(也称为循环缓冲区)是固定大小的缓冲区,工作原理就像内存是连续的且可循环的一样。在生成和使用内存时,不需将原来的数据全部重新清理掉,只要调整head/tail 指针即可。当添加数据时,head 指针前进。当使用数据时,tail 指针向前移动。当到达缓冲区的尾部时,指针又回到缓冲区的起始位置。

环形缓存区的原理和使用并没有普通的链表队列直观,为什么在视频场合推荐环形缓存区,原因是普通队列的出队,入列操作存在malloc和free操作,malloc的频繁操作会引起内存的另一个问题,内存碎片。环形缓存区只会在初始化时malloc一次内存,相比较链表队列而言,有效的避免内存泄露。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
 
static unsigned short validlen;  			    	//已使用的数据长度
static unsigned char* pHead = NULL; 				//环形缓冲区首地址
static unsigned char* pTail = NULL; 				//环形缓存区尾地址
static unsigned char* pValid = NULL; 				//已使用缓冲区的首地址
static unsigned char* pValidTail = NULL; 		    //已使用缓冲区的尾地址
 
 int g_buffer_size = 5;

void InitRingBuffer(unsigned short size)
{
	if(pHead == NULL)
	{
		pHead = (char*) malloc(size);
	}
	pValid = pValidTail = pHead;
	pTail = pHead + size;
	g_buffer_size = size;
	validlen = 0;
}

 
//释放环形缓冲区 
void releaseRingbuffer(void)
{
	if(pHead != NULL)
		free(pHead);
	pHead = NULL; 
}
 

int writeRingbuffer(unsigned char* buffer, unsigned int len)
{
	unsigned short len1, len2, movelen;
	if(len > g_buffer_size)
	{
		return -1;
	}
	if(pHead == NULL)
	{
		return -2;
	}
	assert(buffer);
	
	//将要写入的数据copy到pVaildTaill处
	if(pValidTail+len > pTail)
	{	
		len1 = pTail - pValidTail;              //放到尾部 
		len2 = len - len1;                      //尾部放不下的位置,从头开始放 
		memcpy(pValidTail, buffer, len1);
		memcpy(pHead, buffer + len1, len2);
		pValidTail = pHead + len2;              //新的有效数据 
	}else
	{
		memcpy(pValidTail, buffer, len);
		pValidTail += len;                      //新的有效数据区结尾 
	}
 
	//重新计算已使用区的起始位置
	if(validlen + len > g_buffer_size)
	{
		movelen = validlen + len - g_buffer_size;
		if(pValid + movelen > pTail)            //需要分成两段 
		{
			len1 = pTail - pValid;
			len2 = movelen - len1;
			pValid = pHead + len2;
		}else
		{
			pValid += movelen;
		}
		validlen = g_buffer_size;
	}else
	{
		validlen += len;
	}
	return 0;
}
 

int readRingbuffer(unsigned char* buffer, unsigned int len)
{
	unsigned short len1, len2;
	
	if(pHead == NULL)
	{
		return -1;
	}
	assert(buffer);
	if(len > validlen)
	{
		len = validlen;
	}
	if(validlen == 0)
	{
		return 0;
	}
	
	if(pValid + len > pTail)                     //需要分成两段copy 
	{
		len1 = pTail - pValid;
		len2 = len - len1;
		memcpy(buffer, pValid, len1);           //第一段 
		memcpy(buffer + len1, pHead, len2);     //第二段 ,绕到整个存储区的开头 
		pValid = pHead + len2;                  //更新已使用缓冲区的起始
	}else
	{
		memcpy(buffer, pValid, len);
		pValid += len;                         //更新已使用缓冲区的起始 
	}
	validlen -= len;                           //更新已使用缓冲区的长度 
	return len;
}
 
unsigned int getRingbufferValidLen(void)
{
	return validlen;
}


int main() 
{
	unsigned int readLen,i;
	unsigned char readBuffer[5] = {0};
	
	//unsigned char buffer[25] = {0};
	InitRingBuffer(25);//初始化环形缓冲区 
	unsigned char buffer1[5] = "1234";
	printf("第1次写4字节:%s\n", buffer1);
	writeRingbuffer(buffer1, strlen(buffer1));
	unsigned char buffer2[5] = "abc";
	printf("第2次写3字节:%s\n", buffer2);
	writeRingbuffer(buffer2, strlen(buffer2));
	unsigned char buffer3[5] = "====";
	printf("第3次写4字节:%s\n", buffer3);
	writeRingbuffer(buffer3, strlen(buffer3));
	printf("===================================\n");
	memset(readBuffer, 0, sizeof(readBuffer));
	readLen = readRingbuffer(readBuffer,4);		
	printf("第1次读4字节:%s\n", readBuffer);
	memset(readBuffer, 0, sizeof(readBuffer));
	readLen = readRingbuffer(readBuffer,3);		
	printf("第2次读3字节:%s\n", readBuffer);
	memset(readBuffer, 0, sizeof(readBuffer));
	readLen = readRingbuffer(readBuffer,4);
	printf("第3次读4字节:%s\n", readBuffer);
	releaseRingbuffer();
	
		
	return 0;
}



运行结果

第1次写4字节:1234
第2次写3字节:abc
第3次写4字节:====
===================================
第1次读4字节:1234
第2次读3字节:abc
第3次读4字节:====

 

 

 

 

 

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值