北航OO(2020)第二单元博客作业

第二单元第一次作业

多线程设计策略

第一次作业的想法是设计三个线程:输入线程,调度器线程以及电梯线程。输入线程获取请求并发送给调度器线程;调度器线程通过查询电梯线程的状态(等待、停靠以及移动),并综合已有的请求为电梯线程分配目标;电梯线程根据分配到的目标进行移动,并进行上下乘客操作。

为了这么做输入线程与电梯线程有一共享变量requestQueue用以保存请求,调度器线程与电梯线程有一共享变量taskQueue用来保存电梯的移动目标。这种设计的初衷是让不同线程尽量只干自己该干的事,即输入线程负责请求的输入,调度器线程负责请求的调度,电梯线程负责模拟电梯运动以及人员出入。这么做的好处一方面在于各个线程的职责都十分明确,另一方面在于调度器便于实现更加灵活的调度策略。但这么做的问题在于调度器访问得到的电梯线程状态与电梯线程实时状态并不是同步的,所以在调度器类以及电梯类中都需要加入较多语句来保证调度器能够及时地为电梯更新目标,以及电梯能够正确处理目标。同时另一个问题就是调度器主动分配目标的做法在多部电梯时需要考虑的情形较多,处理起来比较复杂。

度量分析

方法复杂度(其中较复杂的几个)

类复杂度

类图

协作图

本次作业的复杂度主要集中在AlsElevator类以及ElevatorController类中,前者主要与收集电梯运动状态,以及控制运动有关,后者主要与根据不同情况分配目标有关,算是在保证不同类有有不同分工的同时分散了复杂度。同时预留了一个Elevator接口,本来以未之后的作业会加入不同类型的电梯,所以预留接口一边迭代,但由于没有考虑清楚作业的扩展方向,所以最终都没有使用上这个接口。

BUG相关

本次作业在本地测试时一开始遇到的bug主要是线程的退出问题,原先的做法是电梯在无目标时,则睡眠以等待调控器分配目标,但最初由于处理不当导致电梯线程对最终的终止信号响应失败,最终通过增加额外判断,解决了这一问题。

在互测方面,还是以自动测试为主,由于多线程细节方面有很多线程安全的问题,所以自动测试的随机性能取得一定的效果。

心得体会

作为第一次多线程作业,这次作业的起点对我来说还是比较高的。且由于吃了设计缺陷的亏,使得自己在保证线程同步方面耗费了很多精力,也算是告诫自己要先考虑好设计在进行程序的实现吧。

第二单元第二次作业

多线程设计策略

由于这次作业需要完成多部电梯的设计,所以为了降低调度器与电梯之间的耦合度,讲任务分配方案改为电梯主动从调度器获取目标。每个电梯维护自身的目标队列,当目标队列为空则睡眠等待调度器唤醒。调度器负责在有新的请求到来时或者请求未被完全处理完但有电梯在睡眠时向电梯发送通知。当调度器唤醒了多部电梯时,电梯间竞争获取请求。

通过这种处理,较第一次作业来说极大地减少了调度器线程与电梯线程之间的耦合度,并因此大大降低了保证线程间同步的难度。

度量分析

方法复杂度

类复杂度

类图

协作图

从复杂度来看,这次作业相比与第一次作业很明显的就是复杂度集中到了Elevator类中,这是因为Elevator类既需要完成电梯运行的模拟还需要为处理如何获取请求以及请求应该如何安排的问题。从具体的方法复杂度也可以看出Elevator类中关于如何将得到的请求安排到目标队列的方法占据了复杂度的很大一部分。虽然改变了控制策略,但整个程序在大体上也延续了第一次作业中的结构。

BUG相关

在做第二次作业时本地遇到的bug是没有分电梯是否满人来进行请求的处理,出现的情形是电梯已经满人无法继续接受请求,但安排任务的函数没有考虑到这一点而不断为电梯安排这个无法处理的请求导致的。

在互测阶段由于对大多数人来说,这次作业相较于上次作业的只是简单的增加了几部电梯,并且在第一次作业中已经积累了一定的经验,所以这次的尽管也是采用自动测试的模式,但挂了相当长一段时间都没有成功找到他人的bug。

心得体会

虽然大部分人这次作业都延续了上一次作业的风格,但由于我这一次改变了整个控制的策略,就导致需要在Elevator类以及ElevatorController类中做较多变动。但也算是及时止损吧,如果按照上一次作业的思路,那这次作业调度器需要考虑问题复杂度对于我自己来说应该是非常大的。虽然最后程序的复杂度被集中到了Elevator类中,但整体实现起来的难度却相较于上一次作业有所下降。

第二单元第三次作业

多线程设计策略

本次作业对上一次策略进行了较大的延续,但也做了几处更新。第一个更新是对换乘的支持,我才用了一种比较妥协的换乘策略,即自己实现了一个请求类,并将所有请求都分为两步:第一步是能进入哪种电梯,第二步是能从哪种电梯中出来,然后根据进入电梯的情况,有这个自定义的请求类自己生成出电梯的楼层。这样做的话每个请求的运载就被分为两种情形:如果第一次进入的电梯可以直接送达就直接让这个电梯送达,如果不能则请求类的目的楼层为某一个中转楼层以便第二次运输就可以到达终点。第二个更新是电梯的运行方式,之前是以目标队列中下一个目标来进行移动,而这次更新为将目标队列进行升序排列,然后始终往队列两端中离当前楼层较近的一段移动,这种方法较前一种方法可以较少很多细节方面的实现,所以性能可能有所损失,但是起来简单很多。最后一个更新是调度器在给电梯通知的同时会多加一个可选的目的层以帮助电梯减少决策的复杂度。

度量分析

方法复杂度

类复杂度

类图

协作图

对于这次作业新增的换乘请求,我的想法是想模仿人自己处理换乘操作,所以新建了一个MyRequest类,用来处理与换乘相关的操作。由于新建的这个类分担了很多很多有关目标处理的操作,同时也对电梯运行的策略进行了简化,所以在这次作业中几个类相较于上一次作业复杂度都有了一定程度的降低,特别是Elevator类,这个类基本回归了第一次作业的角色,主要处理电梯移动的模拟。除了新增的MyRequest类以外,其他几个类之间的相互关系跟第二次作业相近。

BUG相关

本次作业在一开始在本地测试发现了这样一个bug:由于我电梯的设计是当不再有请求,同时请求队列为空时会开始向下传递终止信号,等电梯运送完这次任务就结束程序,但有时候电梯内部可能有需要换乘的人员,这就会导致人员未被送完程序就结束了。

另一个bug是在互测阶段产生的,最后发现是一个死锁的bug:电梯与调度器之间有一个互相唤醒的关系存在,当电梯状态更新,或是有新请求到来时,调度器会被唤醒;当调度器发现有未处理请求且有电梯在休眠时,调度器会唤醒电梯。但这样一个关系存在一个问题,就是当电梯正好要去睡眠前,调度器检查发现无可调度电梯于是进入睡眠,但下一刻电梯恰好都去睡眠了,于是就会出现互相等待对方的死锁局面,最终通过设置wait时间上限来向这一bug妥协。

心得体会以及总结

这次作业通过MyRequest类的增加,我认为在一定程度上平衡了不同类的复杂度,同时也较上一次作业对类之间的职责有了清晰一点的划分。但我这三次作业中都没考虑过的一个问题就是调度器是否需要单独设置成一个进程的问题,现在考虑来看由于调度器的操作场景完全是在外界状态有更新时(包括有新的请求、电梯状态更新等)才需要进行操作,所以完全可以不需要设置成一个进程,直接让输入进程与Elevator进程把调度器当作一个共享资源来操作即可,同时这样做还有一个好处就是可以不在考虑电梯线程与调度器线程之间的同步问题,也能在一定程度上使得实现更简洁也更安全。

对于程序的扩展性,考虑了临时删除电梯以及乘客中途更换目的的情况。临时删除电梯可以通过将电梯从电梯集中删去,并通过endSignal信号让电梯在运送玩这次任务后就结束来实现。对于乘客临时更换目的地,由于MyRequest类就是一个模仿乘客行为的类,所以通过更改MyRequest对象的一些属性就可以简单地实现乘客的临时换乘。

猜你喜欢

转载自www.cnblogs.com/8igfive/p/12709223.html