301
社区成员
发帖
与我相关
我的任务
分享题目简述:输入会指定不同电梯,完成对应请求。
根据实验上机课代码的架构,搭建该次作业的框架(本单元架构都是基于实验课的架构,不得不说课程组yyds!)。具体而言,根据题目需求设置了Input、Elevator、Request、RequestQueue、Scheduler、Main类以及一个枚举类型Status。
采用生产者-消费者模型,其中RequestQueue类充当共享缓冲区,Input类代表生产者,不断将请求放入缓冲区,Scheduler类代表消费者,从缓冲区中取出请求,然后分配给对应的elevator的候乘队列中,其中候乘队列也是RequestQueue类型。


因为输入指定了捎带电梯,所以不需要特定的调度策略。
该次作业无bug。
题目简述:输入不指定电梯,同时增加了reset请求。
主体框架仍采用hw1的框架,主要增加调度策略和reset请求的处理。
具体而言在Scheduler类里增加一个方法实现调度策略。针对reset请求,考虑对电梯增加一个RESET状态,在Elevator类里新增reset相关操作的方法,从而实现reset请求。
再次感谢课程组实验课代码,yyds again!


目前主流的策略有均匀分配、随机策略、影子电梯、调参函数(黑盒)等。
起初觉得选一个最简单的方式完成本次作业,后面再进行修改,结果在实现过程中出了一堆bug,后面就没时间实现令人向往的性能之巅——影子电梯(主要是太菜啦)。
实现了两种方法,第一种是均匀分配,第二种,也是后面一直使用的是调参的方式。
均匀分配很简单,就是将请求均匀分配给这6个电梯,如果遇上了电梯reset就跳过,直到顺序分配给能够接受请求的电梯。这种策略考虑的因素较少,“均匀”主要是为了解决请求分布不均,导致某部电梯请求过多拼命干活,其他电梯空闲看戏的情况。但是由于该次作业有reset请求,可以让通过构造特殊样例,让“均匀”也不再均匀。
调参策略,就是给电梯打分,分数越高,越优先接受请求。而打分根据打分函数进行,打分函数考虑各种因素,这里主要考虑了电梯速度,电梯最大容纳量,当前轿厢人数,候乘请求数等因素,其中电梯速度(时间)、当前轿厢人数、候乘请求数与分数成负相关,与电梯最大容纳量成正相关。同样如果电梯正在reset将跳过,直到能有电梯接收该请求。
感觉在调度策略方面,主要考虑的是时间因素,没有特别考虑电量。而调参的方式感觉就是简化版的影子电梯,考虑的参数较少,计算过程更加粗粒度。因为完成作业的时间比较紧,就没有更加仔细设计调参策略,考虑的因素也比较少。
通过后面研讨课的讨论,发现调参也可以做到很好的性能,可以把运行方向与请求的运行方向,运行多少楼层才能到请求的出发层等等。
不幸的是,被刀了。
当有5个电梯reset,1个电梯可以接受请求时,所有的请求都会集中在一个电梯里,造成了TLE。
主要原因就是调度策略会直接跳过reset状态的电梯。
可以考虑使用缓冲队列(性能更好)。这里我采用的是暴力的sleep方式,当reset的电梯数量大于4时,就强迫sleep(100)。
题目简述:在之前作业的基础上将reset请求分为两类,引入双轿厢电梯和换乘层。
主要基于上次作业框架增加了双轿厢的机制。
将双轿厢考虑为最低层和最高层不同的电梯。其中主要的问题为电梯在换乘层冲突,请求跨越换乘层。

使用锁,给换乘层上锁。
在这次作业中使用的是ReentrantLock。每个电梯一把锁。当即将达到换乘层时,尝试拿锁,拿到锁后进入换乘层,进行相关操作。
if (isDoubleEle && curFloor == transferFloor - 1) {
lock.lock();
}
同时,换乘层不能久留,规定不会有电梯停在换乘层。离开换乘层前还锁。这样就保证双轿厢不会冲撞,同时不会因为换乘层被久占而发生死锁。
if (isDoubleEle && curFloor == transferFloor) {
curFloor--;
move();
print("Arrive", 0);
lock.unlock();
}

与上次作业相同。
真正的心寒是强测爆点,互测被刀,腹背受敌。
reset accept和reset begin之间arrive的楼层大于2。
主要原因是,未对电梯运行(arrive)进行设置,采用的是优先处理reset请求的方式,保证以最快速度reset begin。但是schedule线程可能被阻塞,elevator线程运行速度过快,导致,未能及时改变elevator的状态(设置为reset)。
因为问题主要出现在电梯运行与状态改变速度不匹配导致。
考虑将电梯稍微sleep一下,降低一下速度,从而让电梯状态能够及时改变。
主要采用代码走读和输出分析。
感觉多线程的debug不容易复现,代码走读确实是必备的技能,同时也可以考虑采用在代码中设置print断点,通过一些多余的输出来辅助分析代码运行逻辑。
同时每个线程是独立的,如果是6号电梯出现问题,就只用看与6号电梯相关的输出就行。
代码的整体框架还是比较好的,没有任何改变。可扩展性不错。同时代码没有很乱,同步块都集中在RequestQueue类中,几乎所有线程wait和notifyAll都在该类中,死锁概率较小。代码耦合程度不高,比较符合“铁路警察各管一段”。
本单元主要是增量开发,需要根据新增的功能要求,局部修改即可,不需要大改框架。容易改变的内容主要集中在Scheduler类和、Elevator类,因为这两个类完成了最主要的逻辑。所以增量开发带来的改变也主要集中在这两个类里面。
Scheduler类中主要改变的是调度器结束的条件以及调度策略、分配方式。
hw2出现了reset请求,会清空电梯,这是调度器结束的条件不只是输入结束了,同时还有因为reset而需重新分配的请求。
hw3出现了双轿厢电梯,这是调度器结束条件还要考虑无法一次性完成请求,需要换乘的请求。
Elevator类中主要增加请求对应的操作。
hw2出现了reset请求,这里我将其设置为一个RESET状态,增加对应的reset操作。
hw3出现了不同的reset请求,我直接在reset操作里,根据不同reset请求采取不同的reset操作。
大名鼎鼎的电梯确实很顶。
这一单元主要是训练多线程面向对象变成能力。看到各种调度策略,各种优化方法,收货很多。量子电梯、影子电梯、调参、随机策略、同步块、读写锁、自旋锁、生产者-消费模型、单例模式、观察者模式、一些很强的容器、volatile关键字等等,还有很多东西需要继续学习。
感觉难度主要是多线程debug以及调度策略的实现。因为时间不够,这个单元还是留有了一些遗憾,属于基本完成了该单元的任务。
同时课程组的讲解,实验课代码的引导,真的帮助了很多orz。没有课程组,不知道这次作业的代码又会是什么样子(肯定混乱无比)。
对于线程安全和层次化问题,课程组提供的实验代码已经帮我解决了。整体的结构很清晰就是一个线程进行读入请求,一个线程分派请求,6个线程完成请求。而要进行线程间数据传递时,就设置一个共享缓冲区,该缓冲区是RequestQueue类型实现,做到了抽象与统一,实在是妙。
同时这种架构因为采用的是生产者-消费者模型,共享区全部是RequestQueue这个类的实例,其线程安全问题也被解决。访问共享区的资源也必定是互斥操作,因为RequestQueue中的方法都被synchronized包围。而wait操作也都在RequestQueue类中(出了一个WakeCnt类型),其中的方法对应都实现了notifyAll,所以不会造成死锁。
感谢课程组老师和助教们,对作业的讲解和答疑,为我们提供了很多帮助。感谢同学们讨论区以及研讨课提出的很好的方法和思想,以及感谢cxc以及ltc、yez等同学提供的很好用的评测机,在debug中帮助了很多。