【某航】队列模型(MM1)设计与仿真

代码链接:github代码

一、 实验目的

应用M/M/1队列编程思想,模拟超市收银排队等待的过程,熟悉离散事件推进方式、队列建立和提取方式。

二、 数学模型

1、 首先确定模型框架,即核心是创建一根事件轴和一支队列。先判定事件轴是否忙碌,是就根据时间先后顺序让顾客进入队列,否则推进事件

构建时序推进的离散事件仿真模型,此处编写代码采用基于活动的方法推进仿真时钟,使用固定时间间隔和基于规则的方法来决定是否开始一个活动,在每到一个固定的时间节点,就检查条件,判定此时处于哪个活动,并触发相应活动的条件,直到整个时间推进超出规定的阈值范围。

2. 简述事件调度法、活动扫描法和进程交互法的异同。
区别(异):
事件调度法:面向事件,记录事件发生的过程,处理每个时间发生时系统状态变化的结果;
活动扫描法:面向活动,记录每个活动开始与终止的时间,从而记录实体从一种状态变为另一种状态的过程。
进程交互法:面向进程,关注实体以及它所经历的事件/活动序列。
相同:
三种方法的核心都是事件按时间顺序发生,即按仿真时钟进行推进。

三、 编程实现与调试过程

1、 分析一下到达和离开的事件处理流程:
在这里插入图片描述

图1 编程流程图(活动扫描法)
代码采用Java编写,JDK>=1.8均可运行该程序,具体的流程实现如下:
  • 初始化:输入平均到达时间,顾客平均服务时间,监控时钟的终点时刻
  • 执行下列循环直到监控时钟到达终点时刻:
  • 若监控时钟位于服务开始时间和服务结束时间之间:
    • 输出此时队列中正在排队的人数,监控时钟切换下一个时间;
  • 若监控时钟不位于在两者之间,且服务开始时间小于服务结束时间(代表下一个顾客的到来):
    • 若排队序列为空:
      • 执行服务活动,并计算出来服务结束的时间,将事件添加到结束的事件中,总服务数++,计算并更新总逗留时间Total_q,总等待时间Total_w,总服务时间Total_s,计算下一个顾客到达时间作为服务开始时间;
    • 若排队序列不为空:
      • 添加该事件Event到排队序列末尾,计算下一个顾客到达时间作为服务开始时间;
  • 若监控时钟不位于不在两者之间,且服务开始时间大于等于服务结束时间(代表当前顾客已经被服务结束):
    • 若排队序列为空:
      • 定义服务结束时间为无穷大,计算下一个顾客到达时间作为服务开始时间;
    • 若排队序列不为空:
      • 弹出排队序列中的下一位顾客,执行服务活动,并计算出来服务结束的时间,将事件添加到结束的事件中,总服务数++,计算并更新总逗留时间Total_q,总等待时间Total_w,总服务时间Total_s,计算下一个顾客到达时间;

2、 给出输入参数(平均到达时间,平均服务时间,时钟截止时间)、输出参数(顾客数目,服务顾客数,平均执行时间,队列中平均等待客户数,服务器利用率)的具体值,可以用图或表的方式展示多次仿真的结果值

运行Main.java中的main函数,得到如下输出:

改变输入参数,用表格的方式统计输出结果:

编号平均到达时间ta平均服务时间ts监控时钟终点顾客数目服务顾客数队列中平均等待顾客数平均等待时间Tw平均逗留时间Tq平均服务时间Ts服务器利用率
10.010.015100983566342610.64715.8000.0150.999
20.0150.0151006612645961.1631.2060.0150.983
30.020.0151003974397200.0400.0550.0150.598
40.0150.015503445330551.1781.2430.0150.988
50.0150.0151208086781061.1871.2320.0150.989

分析:

  1. 通过仿真程序输出的结果和理论计算结果基本吻合,平均服务时间与最终计算的平均服务时间一致,平均逗留时间等于平均等待时间与平均服务时间之和,表明该仿真程序的正确性。
  2. 前三组对比实验(1,2,3)保证平均服务时间和监控时钟终点不变,改变平均到达时间,可以看到当平均到达时间小于平均服务时间时,有很多顾客未被服务到,另外队列中平均等待顾客数特别多,平均等待时间非常长,服务器利用率接近100%;当两者相等时,能保证大部分顾客都被服务上,但队列中仍存在等待的情况,服务器利用率也高达98%;当平均到达时间大于平均服务时间时,基本每一位顾客都可以被服务到,队列中平均等待为0(小数点已经约去),但服务器利用率比较低,仅有59%。
  3. 对比实验(2,4,5)保证平均服务时间和平均到达时间不变,监控时钟即仿真时长改变,可以看到,被服务顾客总人数基本呈现等比例的缩放,平均值基本都保持不变,说明仿真时长基本不对平均结果影响。

总之,通过以上仿真实验可以得出结论,当平均到达时间约等于平均服务时间时,可以兼顾服务器高利用率的同时,让队列中的人数也不至于过多,另外此时大部分顾客也可以被服务到。考虑到超市的实际情况,工作日人流量较小,周末人流量较大,要在考虑经济情况与成本的前提下,做出适当调整。

Controller.java

/**************************
 *  M/M/1 核心类 Controller
 **************************/
package mm1;

import java.util.*;
import java.util.Random;

public class Controller {
	
    public static final int BIRTH = 0;
    public static final int DEATH = 1;

    LinkedList<Event> schedule;     // 事件队列
    double clock;					// 时钟
    double endTime;					// 时钟终止时间
    double nextArrival;				// 服务开始事件
    double nextDeparture;			// 服务结束时间
    double ta;					    // 平均到达时间
    double ts;						// 平均服务时间
    ArrayList<Double> checkpoints;	// 监控测点
    int numChecks;					// 监控测点数
    
    double Tq,Tw,Ts;
    int w, q;
    int requests, serviced;

    /* 初始化 */
    public Controller(double ta, double ts, double endTime) {
        this.ta = ta;
        this.ts = ts;
        this.endTime = endTime;

        checkpoints = new ArrayList<Double>();      
        double n = 0;
        while (n < endTime) {
        	numChecks++;
        	n += exponential(ta);
        	checkpoints.add(n);
        }
        
        clock = 0;
        schedule = new LinkedList<Event>();
        nextArrival = exponential(1/ta);
        nextDeparture = Double.POSITIVE_INFINITY;
        
        Tq = 0; Tw = 0; Ts = 0;
        w= 0; q = 0;
        requests = 0; serviced = 0;
    }
  
    /* 下一个顾客到达 */
    public void birthHandler(double time) {
        if (schedule.isEmpty()) { // 队列为空,执行服务
            scheduleDeath(time);
        } 
        else { // 队列不为空,添加此顾客到队列中
            schedule.add(new Event(time,BIRTH));
        }
        nextArrival += exponential(1/ta);
    }
    
    
    /* 当前服务结束 */
    public void deathHandler() {
    	schedule.remove(); // 事件移除
    	if (!schedule.isEmpty()) { // 队列不为空:弹出执行队列中的任务
    		Event next = schedule.remove();
    		scheduleDeath(next.getTime());     		        		
    	} else{
            // 队列为空,此时服务器空闲,等待下一任务
    		nextDeparture = Double.POSITIVE_INFINITY;
    		nextArrival += exponential(1/ta);
    	}
    }
    
    /* 服务执行,计算相关参数 */
    public void scheduleDeath(double arrivalTime) {
    	nextDeparture = clock + exponential(1/ts);  // 本次服务结束时间
        schedule.addFirst(new Event(nextDeparture,DEATH));
        serviced++;  // 总服务数
        Tq += (nextDeparture - arrivalTime); // 总逗留时间
        Tw += (clock - arrivalTime);  // 总等待时间
        Ts += (nextDeparture - clock);  // 总服务时间
    }
    
    /* 监控输出 */
    public void monitorHandler() {
    	int cur_q = schedule.size();
    	int cur_w = (cur_q > 0) ? (schedule.size() - 1) : 0;
    	w += cur_w;
    	q += cur_q;
    	checkpoints.remove(0);
    }

    /* 指数分布生成函数 */
    public static double exponential(double lambda) {
        Random r = new Random();
        double x = Math.log(1-r.nextDouble())/(-lambda);
        return x;
    }

    /* 主函数 */
    public void run() {
    	while (clock < endTime) {
    		if (checkpoints.get(0) < nextArrival && checkpoints.get(0) < nextDeparture) {
    		    // 监控时刻位于服务发生时
    			clock = checkpoints.get(0);
    			monitorHandler();
    		}
    		else if (nextArrival <= nextDeparture) { // 下一个顾客的到来
            	clock = nextArrival;
            	birthHandler(nextArrival); 
            	requests++;
            }
            else {	// 服务结束
            	clock = nextDeparture;
            	deathHandler();
            }           
    	}
    	printStats();
    }
    
    public void printStats() {
        // 输出相关参数
    	System.out.println("相关计算参数输出:");
    	System.out.println("顾客数目requests: " + requests);
        System.out.println("服务顾客数serviced: " + serviced);
    	System.out.println("队列中平均等待客户数q = " + q/numChecks);
    	System.out.println("平均等待时间Tw = " + Tw/requests);
    	System.out.println("平均逗留时间Tq = " + Tq/serviced);
    	System.out.println("平均服务时间Ts = " + Ts/serviced);
        System.out.println("服务器利用率 = " + Ts/endTime);
    }
}

Event.java

/**************************
 *  M/M/1 事件类
 **************************/

package mm1;

public class Event {
    private double time;
    private int type; 

    public Event(double t, int e) {
        this.time = t;
        this.type = e;
    }

    public double getTime() {
        return time;
    }

    public int getType() {
        return type;
    }
}

Main.java

/********************
 *  M/M/1 队列模型
 *******************/
package mm1;

public class Main {

    public static void main(String[] args) {
    	
        double ta = 0.015;  // 平均到达时间
        double ts = 0.015;  // 平均服务时间
        double length = 100;  // 监控时钟终点
        Controller c1 = new Controller(ta,ts,length);
		System.out.println("仿真 1: 平均到达时间ta = " + ta + ", 平均服务时间ts = " + ts + ", 监控时钟终点 length = " + length);
		c1.run();
		System.out.println("仿真结束");
    }

}

代码链接:github代码

如果感觉对你有所帮助,不妨点个赞,关注一波,激励博主持续更新!

  • 11
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值