首页 > 代码库 > 12.队列的应用-事件驱动编程

12.队列的应用-事件驱动编程

1.事件驱动编程

前面我们提到队列在操作系统中用的非常多,其中一大应用就是基于事件驱动的消息队列机制,熟悉Windows SDK程序设计的对于Windows的消息产生、分发、处理应该不会陌生。

事件驱动编程简而言之就是应用程序维护一个或多个事件队列,完全以事件队列为主线来设计和编写整个程序。如Windows就是以消息队列为中心来对每个消息进行处理从而实现事件驱动编程(如鼠标、键盘等事件的处理)。


2.离散事件模拟

现在为了演示事件驱动编程,我可能并没有能力去构建一个windows消息系统,这里以类似的应用:[银行排队模拟]来演示整个程序。

银行排队模拟描述如下:

假设一个银行有4个窗口对外接待客户。由于每个窗口在某一时刻只能接待一个客户,在客户众多的时候需要排队,对于刚进入银行的客户,如果某个窗口正空闲,则可上前办理业务,如果所有窗口都不空闲则排在人数最少的窗口。现在要求模拟银行的某一时间段内的4个窗口的客户排队情况。这里客户到达的时刻和办理业务的时间都是随机的。


可以看到不同的时刻4个窗口的客户排队情况是不一样的,这个客户排队情况完全是以时间推移来驱动的,那么我们就考虑建立一个基于时间的事件队列,按照事件发生的先后排列事件。这种类似的应用统称为离散事件模拟。


3.实现方式

事件队列元素定义如下

typedef struct
{
	int		nOccurTime;	//事件发生时间
	int		nType;		//事件类型,0-客户到达事件,1、2、3、4-对应窗口客户离开事件
}JWArrayElem_event;

窗口队列元素定义如下

typedef struct
{
	int nArriveTime;		//客户到达事件
	int nDurationTime;		//客户办理业务所需时间
}JWArrayElem_window;


为了实现上述离散事件模拟,这里我们建立一个事件队列,4个窗口排队队列。

初始化上述队列,在事件队列中插入第一个客户到达事件以启动事件驱动。

开启计时,检测是否有事件要发生。

如果是客户到达事件,则设置客户办理业务时间,插入到窗口队列中,同时要插入下一个客户到达事件。如果是当前窗口的第一个客户,还要插入一个客户离开事件以驱动当前窗口的客户离开事件。

如果是客户离开事件,则从对应窗口队列中取出客户并设置下一个客户离开事件,在同一窗口下前一个客户离开事件没有发生之前是不可能发生第二个客户离开事件的。


4.实际程序

#include <stdio.h>
#include <process.h>
#include <stdlib.h>
#include <time.h>
#include <Windows.h>

#include "JWArray.h"

JWArray(event)		*pEventQueue;					//事件队列
JWArrayElem(event)	eventElem;						//事件元素
JWArray(window)		*pWindowsQueue[5];				//窗口队列,为了使用方便,下标从1开始
JWArrayElem(window)	windowElem;						//窗口元素
int					nCloseTime = 100;				//银行关闭时间

JWArray_BOOL EventCompare(JWArrayElem(event) elem1, JWArrayElem(event) elem2);
void ShowState(int nCurTime);
void Init();
void Clear();
int  CalcMinWindow();
void CustomerArrived(int nArriveTime);
void CustomerLeave(int nCurTime, int iLeave);
void EventPump();
void ShowState(int nCurTime);


int main(int argc, char *argv[])
{
	//开启事件泵
	EventPump();

	return 0;
}

/**
 *功能:	事件队列元素比较函数
 *参数:	elem1,elem2 -- 两个事件队列元素
 *返回:	elem1的发生时间小于elem2则返回JWArray_TRUE,否则返回JWArray_FALSE
 *其他:	2014/05/05 By Jim Wen Ver1.0
**/
JWArray_BOOL EventCompare(JWArrayElem(event) elem1, JWArrayElem(event) elem2)
{
	return elem1.nOccurTime  < elem2.nOccurTime ? JWARRAY_TRUE : JWARRAY_FALSE;
}

/**
 *功能:	初始化--分配事件队列和窗口队列,在事件队列插入启动的客户到达事件
 *参数:	无
 *返回:	无
 *其他:	2014/05/05 By Jim Wen Ver1.0
**/
void Init()
{
	int i;

	//分配队列
	pEventQueue = JWArrayCreate(event)(20, 10);
	for (i = 1; i < 5; i++)
	{
		pWindowsQueue[i] = JWArrayCreate(window)(20, 10);
	}

	//在事件队列插入第一个客户到达事件
	eventElem.nOccurTime	= 0;
	eventElem.nType			= 0;

	JWArrayEnQueue(event)(pEventQueue, eventElem);
}

/**
 *功能:	清理工作--清理事件队列和窗口队列
 *参数:	无
 *返回:	无
 *其他:	2014/05/05 By Jim Wen Ver1.0
**/
void Clear()
{
	int i;

	//清理队列
	JWArrayDestroy(event)(pEventQueue);
	for (i = 1; i < 5; i++)
	{
		JWArrayDestroy(window)(pWindowsQueue[i]);
	}
}

/**
 *功能:	计算当前窗口队列前人数最少的窗口号
 *参数:	无
 *返回:	人数最少的窗口队列号
 *其他:	2014/05/05 By Jim Wen Ver1.0
**/
int CalcMinWindow()
{
	int nLength ;
	int i ;
	int nIndex ;
	
	nIndex	 = 1;
	nLength  = JWArrayGetLength(window)(pWindowsQueue[1]);
	for (i = 2; i <=4 ; i++)
	{
		if (JWArrayGetLength(window)(pWindowsQueue[i]) < nLength)
		{
			nIndex = i;
			nLength  = JWArrayGetLength(window)(pWindowsQueue[i]);
		}
	}

	return nIndex;
}

/**
 *功能:	处理客户到达事件
 *参数:	nArriveTime--客户到达的时间
 *返回:	无
 *其他:	2014/05/05 By Jim Wen Ver1.0
**/
void CustomerArrived(int nArriveTime)
{
	int i;
	int nDurationtime;			//当前客户办理业务消耗时间
	int nNextInterTime;			//距离下一个客户到达时间

	//随机生成nDurationtime和nNextInterTime
	srand( (unsigned)time( NULL ) );
	nDurationtime = rand() % 30 +1;
	nNextInterTime = rand() % 5 +1;

	//设置下一个客户到达事件
	if (nArriveTime + nNextInterTime < nCloseTime)
	{
		eventElem.nOccurTime = nArriveTime + nNextInterTime;
		eventElem.nType = 0;

		JWArrayOrderInsert(event)(pEventQueue, eventElem, EventCompare);
	}

	//设置当前窗口排列队形
	i = CalcMinWindow();
	windowElem.nArriveTime = nArriveTime;
	windowElem.nDurationTime = nDurationtime;
	JWArrayEnQueue(window)(pWindowsQueue[i], windowElem);

	//如果是第一个到达客户,插入一个离开事件,否则此后的离开事件在前一个离开事件发生后插入
	if (JWArrayGetLength(window)(pWindowsQueue[i]) == 1)
	{
		eventElem.nOccurTime = nArriveTime + nDurationtime;
		eventElem.nType = i;

		JWArrayOrderInsert(event)(pEventQueue, eventElem, EventCompare);
	}
}

/**
 *功能:	处理客户离开事件
 *参数:	nCurTime--客户离开事件发生时间,iLeave--客户离开事件发生的窗口队列号
 *返回:	无
 *其他:	2014/05/05 By Jim Wen Ver1.0
**/
void CustomerLeave(int nCurTime, int iLeave)
{
	//window队列出列
	JWArrayDeQueue(window)(pWindowsQueue[iLeave], NULL);

	//设置下一个客户离开时间
	if (JWARRAY_FALSE == JWArrayIsEmpty(window)(pWindowsQueue[iLeave]))
	{
		JWArrayGetHead(window)(pWindowsQueue[iLeave], &windowElem);

		eventElem.nOccurTime = nCurTime + windowElem.nDurationTime;//注意这里实际离开时间是当前离开事件发生的时间加上下一个客户要办理业务的时间,不能写成下一个客户到达的时间加上下一个客户要办理业务的时间,这是不对的,因为客户到达时不能立即开始办理,要等待!!!
		eventElem.nType = iLeave;

		JWArrayOrderInsert(event)(pEventQueue, eventElem, EventCompare);
	}
}

/**
 *功能:	开启消息泵,依据时间对事件队列处理从而推动整个事件的发生
 *参数:	无
 *返回:	无
 *其他:	2014/05/05 By Jim Wen Ver1.0
**/
void EventPump()
{
	int i=0;

	Init();

	do 
	{
		if (JWARRAY_FALSE != JWArrayGetHead(event)(pEventQueue, &eventElem))
		{
			while (eventElem.nOccurTime == i)//处理当前时刻可能同时存在的多个事件
			{
				JWArrayDeQueue(event)(pEventQueue, &eventElem);

				if (eventElem.nType == 0)
				{
					CustomerArrived(eventElem.nOccurTime);
				}
				else
				{
					CustomerLeave(i, eventElem.nType);
				}

				if (JWARRAY_FALSE == JWArrayGetHead(event)(pEventQueue, &eventElem))
				{
					break;
				}
			}
		}
		else
		{
			break;
		}

		ShowState(i++);
		Sleep(100);
	} while (1);

	Clear();
}

/**
 *功能:	显示当前事件队列和各个窗口队列的情况
 *参数:	无
 *返回:	无
 *其他:	2014/05/05 By Jim Wen Ver1.0
**/
void ShowState(int nCurTime)
{
	system("cls");
	printf("当前时间:%d\n\n", nCurTime);

	printf("--------------------------------------------------------------------------------\n");
	printf("事件队列\t");
	JWArrayTraverse(event)(pEventQueue, JWArrayPrintfElem(event));
	printf("\n--------------------------------------------------------------------------------\n\n");

	printf("\n--------------------------------------------------------------------------------\n");
	printf("窗口1\t");
	JWArrayTraverse(window)(pWindowsQueue[1], JWArrayPrintfElem(window));
	printf("\n--------------------------------------------------------------------------------\n");
	printf("窗口2\t");
	JWArrayTraverse(window)(pWindowsQueue[2], JWArrayPrintfElem(window));
	printf("\n--------------------------------------------------------------------------------\n");
	printf("窗口3\t");
	JWArrayTraverse(window)(pWindowsQueue[3], JWArrayPrintfElem(window));
	printf("\n--------------------------------------------------------------------------------\n");
	printf("窗口4\t");
	JWArrayTraverse(window)(pWindowsQueue[4], JWArrayPrintfElem(window));
	printf("\n--------------------------------------------------------------------------------\n");
}

程序说明:
1.事件时间单位为秒
2.假设客户办理业务不超过30秒,相邻两个客户到达时间间隔不超过5秒
3.银行在100秒后不再允许客户进入
4.不考虑客户在排队过程中换队的情况
5.这里对于event队列构建了一个顺序插入函数JWArrayOrderInsert(event)

程序运行效果如下



完整事件驱动编程源代码下载链接
原创,转载请注明来自http://blog.csdn.net/wenzhou1219