2020面向对象——电梯架构与策略

电梯架构与策略

三次电梯架构均基于生产者消费者模式,其中homework6和homework7采取的是二级生产者消费者架构。在一个清晰稳定的架构下,可以开始对性能的追求。
此图为homework7的二级模型,Producer是一级生产者,即输入请求线程;Channel为一级托盘,由队列构成;Scheduler为主调度器,一级消费者和二级生产者,负责分配人员进入电梯;SmallChannel为二级托盘,每一个elevator都持有一个类型对应的SmallChannelElevator为二级消费者,电梯线程。

而且在homework7中涉及到换乘——线程结束问题时,通过在Channel中设置一个整体换乘人数记录,可以很方便且线程安全的完成任务。

homework5

SSTF策略的基础上,加入了调头接人的选择。通过三个参数反应电梯状态goal,direction,floor当前目标、运行方向、当前楼层;在托盘的等待队列中查找是否存在掉头接人收益更高的情况,如存在,则回去接人。
如果在移动的反方向存在请求:1.方向与direction一致,且接他的代价小于当前目标-当前楼层;2.方向与direction相反,且完成他的所有花费personGoal-floor小于当前目标-当前楼层;满足1或2条件均掉头接人。但使用这种方法需要考虑清楚是否会出现因两侧均有请求而反复掉头的情况,如出现该情况,电梯会进入上下的死循环。性能在评测的数据中表现较为不错。

public synchronized int[] setDirection(int direction, int curFloor, int goal) {
        int[] ans = new int[2];
        ans[0] = direction;
        ans[1] = goal;
        if (outside.isEmpty()) {
            return ans;
        }
        Person outGoal = getMin1(curFloor);
        int costM = Math.abs(goal - curFloor);
        if (Math.abs(outGoal.getPersonRequest1().getFromFloor() - curFloor)
                < Math.abs(goal - curFloor)) {
            int temp = outGoal.getPersonRequest1().getFromFloor() - curFloor;
            int costT = outGoal.getPersonRequest1().getToFloor()
                    - outGoal.getPersonRequest1().getFromFloor();
            if (temp > 0 && direction == -1) {
                if (costT < 0 || costT < costM) {
                    ans[0] = 1;
                    ans[1] = outGoal.getPersonRequest1().getFromFloor();
                }
            }
            if (temp < 0 && direction == 1) {
                if (costT > 0 || Math.abs(costT) < costM) {
                    ans[0] = -1;
                    ans[1] = outGoal.getPersonRequest1().getFromFloor();
                }
            }
        }
        return ans;

homework6

多部等价电梯,所以对于大部分数据,应该让尽可能多的电梯运行起来,即做到人数大致分派均衡,这样可以取得较好收益。电梯内部的调度算法仍然沿用之前的算法,只需要注意人数满载条件对一些细节的影响。在迭代中主要考虑主调度器中的分配情况,大致思路为:即考虑电梯及其等待队列里拥有的人数,也考虑电梯此时楼层与运行方向与待分配人员的关系,根据这些定义花费函数,将待分配人员分配至花费最小的电梯那里。定义的花费计算函数如下。而cost根据一些状态调整的相关幅度不必过于追求,对于随机数据来说有个大概的分配策略就行了。对于评测数据也表现较为不错。

public int calculate(SmallChannel s, Elevator e, PersonRequest p) {
        int cost = 99999;
        if (!s.isFull()) {	cost = 9999;	}
        int costN = s.getNum() + e.getNum();//s,e均空
        if (costN == 0 ) {	cost = cost - 7;	}
        cost += costN;
        int fx = p.getToFloor() - p.getFromFloor();
        if ((p.getFromFloor() < e.getFloor() && e.getDirection() == -1) //the same direction
                || (p.getFromFloor() > e.getFloor() && e.getDirection() == 1)
                || (p.getFromFloor() == e.getFloor()) || e.getDirection() == 0) {
            cost += Math.abs(p.getFromFloor() - e.getFloor());
            if ((fx < 0 && e.getDirection() == 1) || (fx > 0 && e.getDirection() == -1)) {
                cost = cost + costN + 10;
                cost = cost + Math.abs(fx);
            }
        } else {
            cost += 999;
            cost = cost + Math.abs(p.getFromFloor() - e.getFloor());
            if ((fx < 0 && e.getDirection() == 1) || (fx > 0 && e.getDirection() == -1)) {
                cost = cost + Math.abs(fx) + costN + 10;
            }
        }
        return cost;
    }

homework7

这次作业新增电梯请求指令,并且电梯之间并不等价,存在需要换乘的情况。

  • 电梯调度主调度器分派基本沿用上次的代码.
  • 为了最大限度的利用前一次作业的架构(主调度器只管人员分配,电梯只管接送人员),我建立Person类对PersonRequest进行处理,Person类持有两个PersonRequest和一个isTransfer标志。代码示意如下,其中对mid楼层的选择也较为简单粗暴。(原本想建立根据当前电梯情况选择mid楼层,但因为这样做有较多交互,并且在分配时已经做了各电梯的权衡分配,就选择了硬切割)降低了耦合,最终配合分配、调度效果也较为不错。
  if(isDirect(PersonRequest)){
  	PersonRequest1=PersonRequest;
  	PersonRequest2=null;
  	isTransfer=false;
  }else{
  	PersonRequest1=new PersonRequest(from,mid,id);
  	PersonRequest2=new PersonRequest(mid,to,id);
  	isTransfer=true;
  }
  // 选择mid
  if (from < 3) {
	midfloor = 1;
  } else if (from > 15) {
	midfloor = 15;
  } else {
     if (to < 3) {
          midfloor = 1;
     } else if (to > 15) {
          midfloor = 15;
     } else {
          midfloor = 5;
     }
  }
  • 换乘后的PersonRequest2是这次设计的最大难点,由他产生了线程结束,顺序接送的问题。下面给出我的处理:电梯类内部持有共享一级平台Channel,在PersonRequest1下电梯后,向Channel放入二号请求,之后回到主调度向下分配的操作。但仅仅这样解决不了线程结束的问题,当输入线程结束时,电梯内仍有换乘请求的乘客,则它会因为主调度器线程的结束而不能往下分配。所以需要在Channel中设置一个变量numTransfer记录换乘乘客数量,当其为0时才能进入原来的结束条件。涉及numTransfer的操作在两个地方,主调度器下派时,若该请求为换乘请求则numTransfer++,在电梯线程中若下电梯的人为transfer,则调用Channel中的put7
public synchronized void put7(PersonRequest p) {
        queue.add(p);
        numTransfer--;
        notifyAll();
    }

总的来说,利用生产者消费者模式,将在多个线程中都要进行操作的变量封进一级平台或二级平台中,较好的解决了锁占用与释放的问题,将线程安全大部分交给经典模式控制,可以让我们在整体架构完成后将注意力放在调度、分配函数上。
第三次的性能测评是我感到最意外的一次,因为基于之前的架构(分派、调度分离),想要动态的分割换乘较为困难,直接选用了简单粗暴的可行换乘,而相比同学分享的动态换乘等等操作,我认为性能应该处于弱势地位,但对于这次的评测数据,性能分出乎意料的不错。
三次电梯的调度、分配,都是在保证线程安全基础后,采取普普通通的方法策略(易于实现),最终在评测数据中也表现不错,拿到了99.9969的分数,性价比较高。
协作图:
主线程

主调度器线程:distribute人员分派,createEle依据请求造电梯

电梯线程:下人时查看是否需要往一级平台增加请求,即put7

posted @ 2020-04-15 09:06  我深信诸葛亮  阅读(222)  评论(0编辑  收藏  举报