概述
最近有在校的学生朋友在问我,数据结构中的队列在实际的软件开发项目中有什么样的用处。
大家都知道,队列的特点是先入先出,即数据是按照入队列的顺序出队列的。在实际的软件开发项目中,当一个中间模块需要接收和发送大量的消息时,队列就可以大展身手了。我们可以将接收到的数据存储在一个全局队列中,然后在另外的程序流程中将数据从同一个全局队列中取出来,经过一定的处理之后将消息发送到另外的模块。这样做可以降低程序的性能瓶颈。
本文用实际的C代码示例了简单的数据入队列和出队列的方法,大家可据此了解队列的实际用法,也可参照来实现更加复杂的队列操作。
C代码
/**********************************************************************
* 版权所有 (C)2016, Zhou Zhaoxiong
*
* 文件名称:QueueUse.c
* 文件标识:无
* 内容摘要:示例队列的使用(入队和出队)
* 其它说明:无
* 当前版本:V1.0
* 作 者:Zhou Zhaoxiong
* 完成日期:20160811
*
**********************************************************************/
#include <stdio.h>
#include <string.h>
#include <ftw.h>
#include <pthread.h>
#include <time.h>
// 重定义数据类型
typedef signed int INT32;
typedef unsigned int UINT32;
typedef unsigned char UINT8;
// 宏定义
#define MAX_QUEUE 10000 // 最大队列元素个数
// 结构体变量
typedef struct
{
UINT32 iID; // 编号
UINT8 szInfo[100]; // 描述
} T_StructInfo;
// 全局变量定义
T_StructInfo g_tQueue[MAX_QUEUE] = {0}; // 队列结构体
UINT32 g_iQueueHead = 0; // 队列头部索引
UINT32 g_iQueueTail = 0; // 队列尾部索引
pthread_mutex_t g_mutex_queue_cs; // 互斥信号量
pthread_cond_t queue_cv;
pthread_mutexattr_t g_MutexAttr;
// 函数声明
void PutDataIntoQueue(void);
void GetDataFromQueue(void);
INT32 EnQueue(T_StructInfo tQueueData);
INT32 DeQueue(T_StructInfo *ptStructData);
void Sleep(UINT32 iCountMs);
/****************************************************************
* 功能描述: 主函数
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 0-执行完成
* 其他说明: 无
* 修改日期 版本号 修改人 修改内容
* -------------------------------------------------------------
* 20160811 V1.0 Zhou Zhaoxiong 创建
****************************************************************/
INT32 main(void)
{
pthread_mutex_init(&g_mutex_queue_cs, &g_MutexAttr);
pthread_cond_init(&queue_cv, NULL);
// 在循环中执行入队和出队操作
while (1)
{
PutDataIntoQueue(); // 数据入队
Sleep(5 * 1000); // 间隔5秒
GetDataFromQueue(); // 数据出队
Sleep(60 * 1000); // 每一分钟执行一次出队和入队
}
return 0;
}
/****************************************************************
* 功能描述: 将数据加入队列中
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 0-成功 -1-失败
* 其他说明: 无
* 修改日期 版本号 修改人 修改内容
* -------------------------------------------------------------
* 20160811 V1.0 Zhou Zhaoxiong 创建
****************************************************************/
void PutDataIntoQueue(void)
{
T_StructInfo tQueueData = {0};
static UINT32 iCountNum = 0;
// 对结构体的变量进行赋值
tQueueData.iID = iCountNum;
snprintf(tQueueData.szInfo, sizeof(tQueueData.szInfo) - 1, "zhou%d", iCountNum);
// 计数值累加
iCountNum ++;
if (iCountNum >= MAX_QUEUE-1)
{
iCountNum = 0;
}
// 将数据加入队列(一直等到加入成功之后才退出)
while (EnQueue(tQueueData) == -1)
{
Sleep(1000); // 加入失败,1秒后重试
}
// 打印加入的数据
printf("PutDataIntoQueue: ID=%d, Info=%s\n", tQueueData.iID, tQueueData.szInfo);
}
/****************************************************************
* 功能描述: 将数据取出队列中
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 0-成功 -1-失败
* 其他说明: 无
* 修改日期 版本号 修改人 修改内容
* -------------------------------------------------------------
* 20160811 V1.0 Zhou Zhaoxiong 创建
****************************************************************/
void GetDataFromQueue(void)
{
T_StructInfo tQueueData = {0};
if (DeQueue(&tQueueData) == -1)
{
return;
}
// 打印取出的数据
printf("GetDataFromQueue: ID=%d, Info=%s\n", tQueueData.iID, tQueueData.szInfo);
}
/****************************************************************
* 功能描述: 数据入队列
* 输入参数: tQueueData-队列数据
* 输出参数: 无
* 返 回 值: 0-成功 -1-失败
* 其他说明: 无
* 修改日期 版本号 修改人 修改内容
* -------------------------------------------------------------
* 20160811 V1.0 Zhou Zhaoxiong 创建
****************************************************************/
INT32 EnQueue(T_StructInfo tQueueData)
{
INT32 iRetVal = 0;
UINT32 iNextPos = 0;
pthread_mutex_lock(&g_mutex_queue_cs);
iNextPos = g_iQueueTail + 1;
if (iNextPos >= MAX_QUEUE)
{
iNextPos = 0;
}
if (iNextPos == g_iQueueHead)
{
iRetVal = -1; // 已达到队列的最大长度
}
else
{
// 入队列
memset(&g_tQueue[g_iQueueTail], 0x00, sizeof(T_StructInfo));
memcpy(&g_tQueue[g_iQueueTail], &tQueueData, sizeof(T_StructInfo));
g_iQueueTail = iNextPos;
}
pthread_cond_signal(&queue_cv);
pthread_mutex_unlock(&g_mutex_queue_cs);
return iRetVal;
}
/****************************************************************
* 功能描述: 数据出队列
* 输入参数: ptStructData-队列数据
* 输出参数: 无
* 返 回 值: 0-成功 -1-失败
* 其他说明: 无
* 修改日期 版本号 修改人 修改内容
* -------------------------------------------------------------
* 20160811 V1.0 Zhou Zhaoxiong 创建
****************************************************************/
INT32 DeQueue(T_StructInfo *ptStructData)
{
T_StructInfo tQueueData = {0};
if (ptStructData == NULL)
{
return -1;
}
pthread_mutex_lock(&g_mutex_queue_cs);
while (g_iQueueHead == g_iQueueTail)
{
pthread_cond_wait(&queue_cv, &g_mutex_queue_cs);
}
memset(&tQueueData, 0x00, sizeof(T_StructInfo));
memcpy(&tQueueData, &g_tQueue[g_iQueueHead], sizeof(T_StructInfo));
g_iQueueHead ++;
if (g_iQueueHead >= MAX_QUEUE)
{
g_iQueueHead = 0;
}
pthread_mutex_unlock(&g_mutex_queue_cs);
memcpy(ptStructData, &tQueueData, sizeof(T_StructInfo));
return 0;
}
/**********************************************************************
* 功能描述: 程序休眠
* 输入参数: iCountMs-休眠时间(单位:ms)
* 输出参数: 无
* 返 回 值: 无
* 其它说明: 无
* 修改日期 版本号 修改人 修改内容
* ------------------------------------------------------------------
* 20160811 V1.0 Zhou Zhaoxiong 创建
********************************************************************/
void Sleep(UINT32 iCountMs)
{
struct timeval t_timeout = {0};
if (iCountMs < 1000)
{
t_timeout.tv_sec = 0;
t_timeout.tv_usec = iCountMs * 1000;
}
else
{
t_timeout.tv_sec = iCountMs / 1000;
t_timeout.tv_usec = (iCountMs % 1000) * 1000;
}
select(0, NULL, NULL, NULL, &t_timeout); // 调用select函数阻塞程序
}
程序运行情况
我们将上面编写好的QueueUse.c文件上传到Linux机器上,使用“gcc -g -o QueueUseQueueUse.c”命令编译之后,生成QueueUse文件。之后,执行“QueueUse”命令,即可看到程序的运行结果(结果会不断地更新)如下:
~/zhouzx/Test/QueueUse> QueueUse
PutDataIntoQueue: ID=0, Info=zhou0
GetDataFromQueue: ID=0, Info=zhou0
PutDataIntoQueue: ID=1, Info=zhou1
GetDataFromQueue: ID=1, Info=zhou1
PutDataIntoQueue: ID=2, Info=zhou2
GetDataFromQueue: ID=2, Info=zhou2
PutDataIntoQueue: ID=3, Info=zhou3
GetDataFromQueue: ID=3, Info=zhou3
我们看到,数据先是被加入到队列中,然后再从队列中取出来。
程序说明
第一,在本程序中,入队列和出队列是在同一个函数中完成的,但是,在实际开发项目的程序中,入队列和出队列一般是在不同的程序流程(两个不同的线程)中完成的。
第二,本程序的数据入队列操作是在EnQueue函数中完成的,数据出队列操作是在DeQueue函数中完成的,全局变量g_tQueue用于存放需要处理的数据。
第三,在实际开发项目的程序中,有可能会有很多流程都会调用入队列和出队列的函数,为了防止多个流程同时向队列中加入数据或取出数据,在EnQueue和DeQueue函数中使用了锁操作。也就是说,在操作数据之前,先用pthread_mutex_lock函数执行加锁操作,在处理完数据之后,再用pthread_mutex_unlock函数执行解锁操作。
第四,在实际开发项目中,为了防止程序从队列中取数据的速率过快而使得下游模块处理不过来,我们常在从队列取出数据之后发消息的流程中控制数据的发送速率,具体每秒钟发送多少条可在配置文件中设置。