【Unit2】电梯调度(多线程设计)-作业总结
第一次作业
1.1 题目概述
5座楼,每座楼单电梯,类型相同,请求不跨楼层
1.2 个人处理思路
红色加粗为线程类,绿色块为临界区(共享对象)
/...鄙人还在加班加点的赶制中.qwq./
1.3 Bug记录与分析
本次强测43.99,一个bug,强测hack中了11次(挺神奇这种核心错误竟然还能过中测和部分强测),互测情况类似。
要端正测试的态度,应该说一次作业中,代码和测试应各占50%
1.3.1 个人
[1] 忘记判断elevator.isEmpty()导致电梯调度处理线程提前结束 (强测&互测)
因大体框架按实验三进行,ProcessThread结束条件亦然。后经考量,抽象出Elevator类,模拟电梯状况并记录相应原子操作,供ProcessThread操纵。
然而个人所设ELevator并非独立线程,若未判断电梯是否清空便结束ProcessThread线程,便会导致电梯内残留请求。
真是十分不爽!
明明加Elevator类时有想过这点,想着等一切成型后再来收尾,其后便忘了;清明强行赶冯如杯项目,时间不够且心生倦怠,随手测俩数据便交中测去赶OS实验了;关于测试越来越疲,侥幸心理越来越重...
总之OO状态日趋下行(学期状态也是),B屋是个警醒,今后事情还会很多,希望别再忘本。
1.3.2 他人
因为都是B屋,均有各种奇怪的bug,用“超载”“输出线程不安全”的数据共交了3次,貌似hack出了一些其他怪bug,就不细看了。
p.s.官方包输出进行线程安全的类封装
import com.oocourse.TimableOutput;
public class SecureTimableOutput {
public static synchronized void println(String str) {
TimableOutput.println(str);
}
}
第二次作业
周五下午才开始想,发觉要加的东西还挺多,晚上开写时整个人如坐针毡感觉大难临头。于是不经思索的加类,继承/复用什么的就不管了,代码开始向一坨shi发展。好在Elevator相关模型进行了一定的泛化,想清楚后修改不多,的亏上一次的设计了。
多线程学得挺差,到现在最理解的也就只有synchronized一个method,ReentrantLock还没用过。我们似乎没法直接指定一个线程中某几行代码为原子操作,运行期间不切换线程,只能间接通过设置一些临界区并加锁来实现局限视角下的原子性,得处心积虑地控制且无法完全掌控,真挺难受。
最后幸好赶在周六中午完成,但慌忙之间很多东西没有细细琢磨;最后优化了一下午的代码反而又出现运行线程关不掉的事情,git部分文件版本回退操纵不熟悉,心烦意乱就交了没有任何调度策略的初版。但凡我提前一天晚上开始写也不至于这么狼狈了。
2.1 题目概述
- 5座楼,每座初始单电梯,在同楼座中上下跨楼层运动,可在运行时通过请求指令加楼座电梯
- 10层楼,每层初始无电梯,在同层中左右跨楼座运行,可在运行时通过请求指令加楼层电梯
- 乘客请求不会既跨楼座也跨楼层
2.2 个人处理思路
/...鄙人还在加班加点的赶制中.qwq./
2.3 Bug记录与分析
本次强测91.84,强测互测没被hack出,可谓谢天谢地。
改进版出了问题没时间修bug,交了使用随机调度策略的初版,自然没有性能,下次注意。
一直没空写测评机啊,感觉是很大一块缺失。
2.3.1 个人
[1] 楼层调度线程的开关问题
个人处理时一开始没看清楼层电梯初始不存在,于是Main中直接把楼层调度类(FloorProcessThread
类)线程打开了。这样的话,在其下的电梯列表为0时,应该进入wait状态,直到共享的电梯列表类(ElevatorThreadQueue
,别问为啥写Queue,为了和PersonRequestQueue
对称防止看错)加入电梯或总体分配类线程(ScheduleThread
)结束
[2] 优化调度策略+一些其他修改后,运行过的电梯调度线程+电梯线程关不掉
我所谓的优化,便是将一次调度将一定数量的同向&顺路请求交给同一电梯,电梯找当下最远请求,便能在路上最大捎带。
而bug原因在于,从getNearestRequest()
改为getFarthestRequest()
时,max初值设为了0(当时想着楼层没有0,肯定取不到,笑死怎么回事),然而电梯和请求在同层时相对位置就是0,所以导致请求队列有请求而取不出,异常循环停不下来了
giao!
[3] 同样是改为getFarthestRequest后带来的bug
原因在于arriveTo()
方法运行步骤有缺陷,该缺陷可与getNearestRequest()
适配,而不与getFarthestRequest()
适配。
之前的arriveTo(int to)
:
- 若to和当前电梯位置重合,则当前若
elevator.hasOut() || hasIn(null)
,进行一次出入 - 若to和当前电梯位置不同,则向to的方向移动。循环过程:
- while(to和当前电梯位置不同) {移动一层 -> 若
elevator.hasOut() || hasIn(orient)
,则进行一次出入}
- while(to和当前电梯位置不同) {移动一层 -> 若
问题手:
上述ariveTo()
搭配getFarthestRequest()
35-FROM-B-1-TO-B-4
47-FROM-B-10-TO-B-7
开局主请求为47-FROM-B-10-TO-B-7
,所以直接移动,不会捎带35;运行到10层时退出循环,主请求未进入电梯,结束当前主请求;
然后主请求变为35-FROM-B-1-TO-B-4
,47同样未进入电梯(若为getNearestRequest()
,则此时主请求为47-FROM-B-10-TO-B-7
,然后将其装入电梯),直接向1层运行;
如此循环往复
修正:
修改一下arriveTo()
中的运行逻辑:
//get step (orient)
while (curPos != to) {
executeInOut(step);
elevator.move(step);
curPos = to < 65 ? elevator.getFloor() : elevator.getBuilding();
}
executeInOut(null);
2.3.2 他人
没啥空,感觉互测要想有所收获,付出的成本(好好读代码)挺高,权衡了下收益这次就没hack人。
[1] 增设电梯请求后没有乘客请求,导致线程未关闭
别人那儿看来的一个hack数据,感觉挺不错。估摸就是如标题描述,wait后没有notify起来
第三次作业
虽说想提早写,但最后还是周五开始的(下午基物实验,结果和上周差不都多)。一问周围同学好像大都周五前不会动作业,似乎就安心了很多。。。
既然当初没有选择自由竞争,也没太多时间想了,那就调度到底吧。从来没有那么感谢过多态,迭代过程出奇的舒服。
3.1 题目概述
- 5座楼,每座初始单电梯,在同楼座中上下跨楼层运动,可在运行时通过请求指令加楼座电梯
- 10层楼,初始1楼有一全座可达单电梯,在同楼层左右跨楼座运动,可在运动是通过请求指令加楼层电梯
- 乘客请求可能既跨楼座又跨楼层,所以要考虑换成
- 楼层电梯追加可达区域,所以分配时要增加判断
其余新增可定制电梯人数、运行速度等不足道
3.2 个人处理思路
继承原请求类构造新类——多阶段请求类
/...鄙人还在加班加点的赶制中.qwq./
3.3 Bug记录与分析
本次强测82.75,被hack3个点,再进B屋。无可抱怨(),是我楼座调度欠考虑。互测因为相同的问题被hack两个点
只写了数据生成器(这个容易),输出数据正确性判断似乎十分的纷繁复杂,在时间和兴趣不太够的情况下,知难而退了,导致个人测评姬流产。
3.3.1 个人
[1] 若是本身请求仅跨楼层,则stage=2运载结束时即可完成请求
此类思维疏忽,靠测试查出。但这种bug的多少,也衡量出一个程序员水平的高低吧。。。
[2] 改为流水线模式后可能引发的忙等情况
因为迭代为流水线模式,设置了TransferCounter的单例模式记录正在中间运输处理的请求。ScheduleThread线程结束的条件改为waitQueue.isEmpty() && waitQueue.isEnd() && TransferCounter.getInstance().isEnd()
。
当waitQueue
为空而TransferCounter并没有setEnd
,则线程不会退出。继续运行至waitQueue.getOneRequest()
时,会拿到锁并因为队列空而进入等待。此时尚有完成阶段任务的请求想要重新加入waitQueue
,进入忙等。
解决:将waitQueue
共享给TransferCounter在完成阶段任务后TransferCounter.getInstance().release()
中加入waitQueue.notifyAll()
以唤醒线程结束忙等。
[3] 楼层调度策略的可达性判断有误(强测&互测)
我在ScheduleThread中设置了accessMap[11]
记录每个楼层在当前加入的电梯下可达的楼座,然而记录的方式却是将所有楼层电梯的mask全部或起来。这种方式的错误在于两条路径覆盖的点集中,未必两两结点可达。
例如:5楼横向电梯中,电梯1可达A、C,电梯2可达B、D,此时请求A->B显然不能被此时的5楼电梯处理。然而我设计的调度器会因为accessMap中ABCD的mask都在,将该请求放入5楼请求队列中,而此时5楼队列无电梯可接送,所以就忙等了。
解决:静态的调度,所以就将accessMap
改成ArrayList<HashSet<Integer>>
,记录每个加入电梯的mask依次比较再决定调度。似乎挺没技巧但改动最小。
他人
由于流产的测评姬,测试一事显得极为麻烦。又是无hack的一周。。
总结
4.1 关于同步块的设置和锁的选择
同步块和锁均用在了共享对象里,正如前文所述,利用对共享对象加锁间接操纵线程的运行顺序。
实现:对共享对象进行了类的封装,在类的方法中使用synchronize
关键字加锁,并在有线程调用竞争的synchronized
方法结束时进行notifyAll()
4.2 调度器设计
调度器其实就是执行请求分配相关操作的一个线程类。
- 第一次作业仅有的调度器为,将请求从watiQueue分配到每座楼的processQueue。(ProcessThread为电梯的一部分-策略部分,不是调度器)
- 第二次作业采用传统的调度策略,所以正式出现两级调度器:
- ①ScheduleThread: 将waitQueue分配到各楼层/楼座的processQueue
- ②BuildingProcessThread/FloorProcessThread: 将processQueue分配到相应可运载的电梯中elevators(elevators的元素为整个ElevatorThread,是为了拓展优化调度算法时方便获取电梯信息)
- 第三次作业同第二次作业,只不过将一级调度器ScheduleThread变为单例模式
没有好好比较自由竞争和调度器的各自状况
4.3 心得体会
再一次深刻体会到计算机相关行业的价值观——结果导向。没有正确产出的代码,怎么样都像是耍流氓。我们通过黑箱测试找bug,社会也通过黑箱筛选我们。很残酷,却也在理。个人历程的色彩及意义,封闭在以个人命名的对象中,社会的调用只需你满足相应的接口设计即可,两不相干,才能高效调度。
突然有些恐慌,对于受人之托的程序员来说,自己在写在培育的代码,其意义来源却是他人的需求。被别人召之即来挥之即去的,是我们的心血,这多么可悲。倘若想能为自己而写代码,目前的水平下没多少出路。
多线程整个单元的作业都做得比较急,没有思索和额外拓展,所学仅为代码所写的内容,着实不够。不过在层次化设计方面,切切实实的体会到了多态的好处(说:谢谢多态!),使思路清晰工作量大减。
测试方面,感觉测评姬的搭建要比第一单元难了不少,而没有测评姬辅助,靠人力很难进行自我强测检验,互测时同样困难(看别人代码过于痛苦)。课程组是不是可以提供些许测评姬搭建的台阶呢。
同时,中测数据点设置的过于弱导致强测翻车的话,分数很难看且感觉不算太公平(承认,很大部分是自己测试的原因),是否可增强一些中测数据点,比如强测数据点普遍涉及的数据特征应该在中测中有所体现,以防强测大量hack同质bug。
最后,其实看DS、CO、OO、OS,在得分上真正掣肘人的地方不在知识本身而在于debug能力上。都说向死而生,鉴于本人写bug能力,便应时刻“向bug而写”,也为debug设伏。