OO第二单元总结
1. 设计策略
-
-
一个线程用来读入请求
-
一个线程用来调度
-
一个线程用来模拟电梯运行
-
基于这个设计,线程之间的共享对象也很容易得出:
-
-
读取线程与调度线程之间共享读取到的请求队列
-
调度器基于一定的(第一次作业实际上没有)算法逻辑后将各个请求按调度顺序放入调度队列
-
电梯线程与调度线程便共享这个调度队列
-
但是在第二次作业中,我设计的判断是否捎带是放在电梯线程中完成,而判断是否捎带的请求如果还是来自调度队列显然不符合逻辑,因此我将调度线程直接砍掉,只留读取线程和电梯线程,而电梯线程的功能则稍微复杂了那么一点:
-
-
从读取到的请求队列中取出最早到达队列的请求并当作主请求执行
-
执行主请求的过程中每到一层楼,判断是否有人出入,即判断是否有电梯里的人到达目的地,是否有电梯外的人可以捎带
-
这次作业因为不能保证优化完一定不会超过Tmax,我并没有进行优化,算是留有遗憾。
第三次作业多部电梯,是基于前两次作业的基础,并没有特别大的改动,添加的地方不过:
-
-
调度线程回归,调度器里每个电梯各一个队列,负责执行各自电梯的请求
-
从读取到的请求队列取出请求后,按照一定的逻辑判断将该请求放入哪个电梯请求队列
-
若是需要换乘,则需要将请求拆分成两个请求分别加入两个电梯队列,且需等前一个请求完成后,后一个请求方能生效
-
第三次作业我的设计逻辑并没有出现大的问题,也进行了一定程度(极其微小)的优化,但是却出现了一个与线程安全无关的Bug,下文会说到。
2. 作业分析
- 第一次作业
这一次作业我写的还算清晰,没有出现复杂类和复杂方法的情况,说明分工比较合理,当然也是这次作业实现功能比较简单的缘故。
- 第二次作业
这一次作业完成得仍然不算复杂,但是由于设计的问题,我给电梯加入了太多功能,导致电梯类的复杂度偏高。
同时每运行一层楼检查是否有人上下的函数写的也略复杂,但总体来说还算是比较完整的设计。
可以改进的地方便是将判断捎带的功能另外整合到调度器中,但当时为了理解起来更直观并没有这么做。
- 第三次作业
可以看到,第三次作业复杂度已经大面积飘红了。
电梯类沿袭的是第二次作业的电梯,复杂度稍高。
调度器由于需要判断请求是否需要拆分,分配给哪部电梯最省时,写的比较复杂,但确实也想不到什么其他更好的方法来帮助调度器分配任务了。
希望本次作业结束时老师可以公开这次作业写的比较优秀的代码来供我们其他同学学习,我相信会对我们有非常多的启发。
- UML Sequence Diagram
单电梯
多电梯
-
Single Responsibility Principle
-
Open Close Principle
-
Liskov Substitution Priciple
-
Interface Segregation Principle
-
Dependency Inversion Principle
-
这三次作业中我最需要改进的地方是SRP,即每个类或方法都应该只有一个明确的职责。
由上面的类图也可以看出来,我在电梯类和调度器类里面分配了过多的职责和功能,导致类复杂度和方法复杂度一片飘红。
虽说这么做写的时候很爽,但是之后维护起来确实会带来很多问题,不仅稳定性不好,在查错的时候也经常牵一发而动全身。
在以后的编码过程中应该时刻提醒自己这个问题,不能仅仅是凭着直觉一路往下敲代码,还要在写之前合理分配好功能和职责,让我们的设计逻辑更加清晰,架构更好更稳固。
3. Bug分析
第三次作业的Bug是因为对迭代器的理解不深,在迭代的过程中直接删除队列中的执行完了的元素,产生了异常。
这个错误实际上完全可以通过测试检查出来,但当时不知道为什么测了几组临界数据都没有报错,也是这几次作业中最让人懊悔恼火的地方。
我找到其他同学的Bug并不多,实际上还是盲找的,只是交了几组请求条数达到上限的极端数据,没有什么值得点出的地方。
这次找Bug我利用了脚本帮助我测试房间内其他成员的程序,虽然数据仍然是我手动构造的,但是比起上一单元的测试还算是前进了一步。
与上次单元相比,这次构造数据的主要目的是尽可能的多扰动线程间的交流,比如第三次电梯尽可能多地加入换乘请求,来测试程序的线程间是否安全。
但是和第一单元很不一样的一点是,多线程的Bug复现有难度,很多时候本地测试出的Bug,交上去并不能保证可以直接Hack别人,这也是值得注意的地方。
4. 心得体会
虽说同学们都喜欢说多线程是玄学,debug不方便,本地测试正确但一提交就错误,如此种种,但其中很多设计思想和理念在实际运用中十分常见有用,毕竟现实世界本身就是一个多线程的程序,每个人都在并发地干着自己的事。
而通过这一单元的学习,我也对多线程有了一定程度的认识,希望在以后的学习中能够继续加深自己的理解。