2022年北航OO第二单元总结

一至三次作业总结

1. 第一次作业

1.1 需求分析

本次作业要求我们能够在ABCDE五栋大楼中让乘客从出发位置到达目标位置,指导书推荐使用ALS算法,需要使用多线程进行实现。

1.2 实现方案

这次作业是第一次接触到多线程的知识,也是这个单元我认为最痛苦的一次作业,为了为后面两次作业打好框架并且避免多线程相关问题的发生,我在开始搭建框架前学了好一会的多线程的知识。还好好理解了上机的代码,对多线程熟悉了之后,我决定将RequestQueue,即请求队列对外方法完全锁住,从而实现在外部调用方法的多线程特征隐藏,我三次作业中所有的请求“托盘”都是这个类的实例。同时,使用生产者与消费者模式,由输入作为生产者,调度器负责将生产者“生产”的请求放入对应楼座的请求队列中,同时进行唤醒,这里为了避免轮询我在RequestQueue中使用while替代if进入wait(), 以此来避免CPU时间超时。每个电梯处理器为响应大楼的消费者,其控制电梯的一切行为,电梯中则对应着相应ALS实现手段和大量的供处理器获知电梯当前信息的接口,同时处理器拥有电梯的绝对控制权。电梯中UML图如下:

其中程序运行实现的逻辑主要如下:

  1. 电梯分状态运行
    空闲状态:调用RequestQueue::getRequest()方法,若其中没有请求且整个程序未结束,电梯陷入wait(),直到生产者产生新的请求notifyAll()后进行响应。
    主请求未搭乘状态:由空闲状态获取新请求切换至此状态,此时电梯目标位置设置为主请求出发位置,此时电梯只可携乘目的地在电梯目的位置之前或目的地的乘客,即保证一定可以接到主请求。
    主请求搭乘状态:由主请求未搭乘状态切换而来,此时电梯目的地设置为主请求目标位置。同时,为了避免主请求到达时电梯陷入空闲状态而多出0.4秒时间,此时电梯可携乘目标方向一致的所有乘客,主请求到达后,从此电梯中寻找到达位置最近的请求为主请求,若电梯无人,则乘客调用RequestQueue::getRequest(),或重新设置主请求,或陷入空闲状态。

  2. RequestQueue提供所有查询接口
    为提高程序的并发性,我并没有在RequestQueue中直接设置for循环返回请求集合,而是若存在相关需求(比如可携程或者优化的携程需求),即可返回,否则返回null,处理器接受到null后即不再循环调用,这种方法减少了程序对RequestQueue的占用时间,因此一定程度上提高了程序的并发度。

1.3 结构分析

度量如下:

Class metrics:

class OCavg OCmax WMC
Schedule 3.00 5 6
RequestQueue 2.00 5 20
Request 1.00 1 8
Process 3.60 8 36
Main 2.00 2 2
InputThread 2.00 3 4
Elevator 1.23 3 32
Total 108
Average 1.83 3.86 15.43

Method metrics:

CogC ev(G) iv(G) v(G)
Total 126 89 117 134
Average 2.14 1.51 1.98 2.27

本次作业中由于在写代码前分析架构分析了好久,因此代码调试成功之后并没有出现BUG,进行程序反演与自己构建的不同数据进行测试之后,强测也得到了95分,也很幸运在互测中没有被Hack。

1.4 测试思路

同一栋楼使用大量同时到达请求进行轰击,同一层使用大量请求进行轰击,并在代码过程中输出电梯状态实时状态观测,查看有无BUG。

2. 第二次作业

2.1 需求分析

第二次作业需要在第一次作业之上增加环形横向电梯,并且实现可动态增添电梯的行为,乘客需求增加了横向需求。

2.2 实现方案

通过对此次的作业的分析,我发现横向电梯暴露给处理器的接口与纵向电梯完全一致,而且横向电梯除了运行方式之外与纵向电梯完全一直,因此我创建了抽象类BaseElevator,其中只有run()为抽象方向,然后将电梯所有属性与公共方法同一放在其中,之后让其子类继承其并在两子类VertEle和CrossEle中实现抽象方法。除此之外,我构建了Position类来替换作业一中所有的楼层值,其作为横向电梯与纵向电梯在传递数值的通货,同时在此类中封装判断横向运行方向的方法:CrossRightOrNot()由横向电梯调用来进行运行,至此,这一部分新增的功能全部实现,并且修改后原代码逻辑几乎保持不变。之后对于响应构建电梯的请求,只需要增加新的ElevatorRequeue类来进行对电梯请求的收集,我还构建了Maker类来构建新的电梯与电梯控制器,同时实现绑定。这样输入线程将电梯请求交给ElevatorQueue供Maker进行消费,将乘客请求交给Schedule进行分发到不同电梯托盘供各个消费者进行消费。程序的UML图如下:

2.3 结构分析

Class metrics:

class OCavg OCmax WMC
VerticalEle 2.00 3 6
SP 0
Schedule 4.00 7 8
RequestQueue 3.44 10 62
Prequest 1.00 1 4
Position 1.29 2 18
Maker 3.00 5 6
Main 3.00 3 3
InputThread 3.00 5 6
EleRequest 1.29 3 9
EleControl 3.78 11 34
CrossEle 2.00 3 6
BaseEle 1.12 3 29
Total 198
Average 2.04 4.46 14.14

Method metrics:

CogC ev(G) iv(G) v(G)
Total 278 162 182 225
Average 2.87 1.67 1.88 2.32
2.4 测试思路

本次测试我首先利用到了上次的BUG修复,成功发现我在检测电梯是否满载的过程中控制流设置错误,导致电梯人员可以超过上限,修改完此BUG后,我对同层乘客多个电梯竞争接乘进行了大量测试与形式验证,同时测试了单层电梯运行方向。最终在强测中得到了95分,也算是一个我比较理想的成绩。

2.5 总结反思

本次作业迭代我对架构的改变就只是在对“通货”的改变上,首先是横向电梯继承上减少了许多代码的改动,之后又是对整体程序里传递数据的Position(Floor + Build)的相关改动,使得程序十分简洁,其他则是对新增函数的完善,但由于认为自己在多线程的运行过程已较为熟练,并且前两次作业迭代到目前整体结构可扩展性(自认为)很好,这样的错觉让我在本单元第三次作业犯下了一个重大BUG,导致前五次作业均分95+功亏一篑,甚至那晚辗转难眠......

3 第三次作业

3.1 需求分析

第三次作业增添了换乘和横向电梯可达性的需求,其次增加了可定制多种不同类型的电梯功能。

3.2 实现方案

由于前两次作业的架构,对于电梯定制化将承载上限与速度仅仅在BaseElevator中新定义属性,再又Maker线程解析传入参数即可,对于换乘需求呢,我的设计是将一对多生产者消费者模式变为多对多,即每个电梯控制器都是一个生产者,与输入线程共享一个托盘,自定义乘客需求类新建fromFinalPos与tarFinalPos属性,输入线程当检测到该请求为换乘请求时使用这两个参数对乘客需求进行初始化,即转化为同层或同栋的需求。乘客出每个电梯时根据当前位置与最终位置判断是否到达,到达则该任务完成,否则修改乘客需求,返回到调度器中,而初始化乘客需求与修改乘客需求方法全部封装在乘客需求类中,此外我构建了新的共享资源类NumMonitor,输入线程检测到换乘乘客时调用加一方法,电梯控制器校验任务完成后调用减一方法,此类值为0也作为整个程序结束的一个条件,对于可开门,每辆电梯在构建时将会进行注册,注册信息与输入线程完全共享,输入线程因此通过横向电梯所有的注册信息判断中转乘客的中转楼层,将其作为参数传递给请求初始化函数,再在横向电梯运行方法中使用while,每当校验楼座可开门时才跳出,程序UML图如下:

3.3 结构分析

Class metrics:

class OCavg OCmax WMC
VertiaclEle 2.00 3 6
SP 0
Schedule 4.00 7 8
RequestQueue 3.20 10 64
Prequest 1.17 2 14
Position 1.38 2 22
NumMonitor 1.00 1 4
Maker 3.00 5 6
Main 4.00 4 4
InputThread 3.33 5 10
EleRequest 1.29 3 9
EleControl 4.11 11 37
CrossQueue 3.00 11 48
CrossEle 2.50 5 15
CrossControl 4.11 11 37
CanStropFinder 2.00 4 6
BaseEle 1.11 3 30
Total 320
Average 2.29 5.44 18.82

Method metrics:

CogC ev(G) iv(G) v(G)
Total 483 255 297 364
Average 3.45 1.82 2.12 2.60

本次作业的各项复杂度相比前两次改变并不大,因此复杂度指标并没有太大的变化。

3.4 总结反思

其实第三次作业的设计有一个很明显的BUG,说真的,我在当初写的时候似乎有印象,但写着写着就忘了......BUG在于注册信息中,电梯是会判断已注册的横向电梯并对请求是否可达进行校验,当满足后该楼层即可能作为中转楼层,这里是保证了乘客在该中转层有可以对应转移的电梯,但如果该层有两座电梯,他被抢走了地话,就会在电梯里转个不停......比如6层有两座横向电梯7号和8号,7号电梯A,C楼座可达,8号电梯A,B座可达,1号乘客由于注册器信息检测到有8号电梯可转乘B座,且楼层最优,因此被转运到6层,然而此时7号电梯恰好到达A座,根据可携程原则将其搭载,此时1号乘客就和电梯在整个楼层中转个不停.......这也是由于太浮躁没有注意电梯运行过程中犯下的大错,由于这个BUG我的强测爆了6个点,还是感谢助教的心理辅导,不然感觉真地晚上就要睡不着了。

架构设计体验

回忆这三周的OO作业,最艰难的便是第一周了,由于电梯在现实生活中十分常见,因此其架构设计并没有花费我大量的时间,主要是在多线程的学习之中,为了让之后的作业不会因为基础知识的掌握问题而产生问题,我在多线程的学习上花费了大量的功夫,然而就当挺过前两次之后,第三次由于没有校验搭乘爆炸,让我还是没能很好地结束这个单元的学习,有趣的是,那周的OS考试的Extra也没做出来,心里真地好难受。

心得体会

通过本单元的学习,很大地增强了我对多线程的理解和写代码一定要谨慎心细的教训,不能在写代码的时候浮躁,不过失去的分现在也已经无能为力了,那周的电梯爆炸和OSExtra真地好痛苦好难受,不过还是要挺过去的呀!