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