【Unit2】电梯调度(多线程设计)-作业总结

第一次作业

1.1 题目概述

5座楼,每座楼单电梯,类型相同,请求不跨楼层

1.2 个人处理思路

image
红色加粗为线程类,绿色块为临界区(共享对象)

/...鄙人还在加班加点的赶制中.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 个人处理思路

image

/...鄙人还在加班加点的赶制中.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),则进行一次出入}

问题手
上述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设伏。

posted @ 2022-04-07 01:00  Xlucidator  阅读(158)  评论(2编辑  收藏  举报