三次作业的架构和迭代
1. HW-5
1.1 同步块设置 与 锁的选择
主要是对RequestList的读写方法加了锁,调度器要从中取需求,同时输入线程又会向其中加需求。如果对其的访问不放在同步块中的话,有可能会出现对列中的内容一边被修改一边被遍历访问的情况,这时候就可能会抛出异常。设置同步块用到的锁就是最基础的synchronized锁。
1.2 UML协作图和UML类图
UML协作图,对协作图的解释如下:
这里将策略类分离出来,电梯线程需要拿策略时实例化策略类,将自身属性传入,得出策略即可。
电梯策略采用LOOK策略,也就是现实生活中的电梯策略,符合我们日常生活中的直觉。
1.3 BUG及DEBUG
本次作业逻辑比较简单,不涉及选择更合适电梯去接人或者其他RESET请求,因此没有bug,也没有发现别人的bug
找BUG方法:手造高压数据+评测机跑数千次
高压数据比如:在49.9s输入最多数量的请求(满足hack规则下)。
2.HW-6
2.1 新增要求
- RESET请求
- 不再指定电梯接人
2.2 要求分析
- 将RESET视为电梯的一种策略,由策略类给出
- 重写调度类,满足“选择最为合适的电梯去接这个人”的原则
2.3 同步块设置 与 锁的选择
与HW-5没有区别,因为只涉及到RequestList的读写,因此只需要对涉及到RequestList的读写的代码部分加锁即可。
2.4 UML协作图和UML类图
UML类图:
ADVICE新增了两个建议:FLUSH、RESET。
事实上,RESET之前,电梯要尽快把人清空,因此一旦电梯接受到RESET信号,先判断电梯内有没有人,有人则 FLUSH->RESET,没有人则直接RESET。
UML协作图与HW-5的逻辑相同。
2.5 调度策略
调度策略使用电梯遍历的方法:
1.先寻找顺路能捎带的电梯:
如果该电梯人未满且不处于重置状态(即可以装人),且会路过请求楼层,且方向相同,则由该电梯去接人。
2. 无顺路电梯时:选择距离请求楼层最近的电梯接受请求
2.6 BUG及DEBUG
BUG:
代码中存在轮询导致CTLE,调度器类写的时候没有发现轮询问题,在循环中添加一行打印“111”发现生成了100+MB的out.txt发现了问题,将调度器在无需处理请求时设置为wait状态,等待唤醒即可
DEBUG:
1.高压数据
2.输入结束前输入RESET请求
这些较容易出bug的地方hack了一下,高压数据成功hack到3人
3.HW-7
3.1 双轿厢不碰撞的实现
电梯碰撞只会有一种情况:AB中一个在换乘楼层,一个距离换乘楼层只要一层且得到了MOVE的策略,要求向换乘楼层方向移动一层,因此如果在每次得到MOVE策略之前进行判断,如果MoveCanHit()函数为TRUE,就把建议改为WAIT,直到另一电梯离开换乘楼层,再将其唤醒即可
3.2 同步块设置 与 锁的选择
主要是对RequestList的读写方法和elevator的读写方法加了锁,HW-7中把电梯从电梯线程中抽离出来封装成单独的一个类,因此elevator属性的改写也要加锁,因为1-A号电梯的线程是需要读取1-B的部分属性的。
3.3 UML协作图和UML类图
UML类图:
新增了elevator类用来存放12部电梯的基本属性
UML协作图与HW-5相同,区别仅仅是从6部变为12部电梯。
3.4 调度策略
因为这次电梯有可能出现不能把人送到目的地的情况,因此调度策略有改变
调度策略的方法:
1.先寻找能把人送到目的地的电梯:
2.如果没有这样的电梯:
2.1.先寻找顺路能捎带的电梯:
如果该电梯人未满且不处于重置状态(即可以装人),且会路过请求楼层,且方向相同,则由该电梯去接人。
2.2 无顺路电梯时:选择距离请求楼层最近的电梯接受请求
3.5 BUG及DEBUG
BUG:
存在线程异常结束的问题:在input结束后可能出现把最后一个乘客送到换乘楼层去换乘但是因为此时乘客remove却还没进入waitlist,导致判定为结束,最终没有送到目的地。因为多线程电脑不同的原因,该bug在本地不会出现却在评测机上有可能出现,大概同一份代码测两次会出现一次这样的问题,没有想到特别好的方法,只好采用 sleep让线程睡几秒,不能那么快结束,舍弃一部分性能换取正确性。
DEBUG:
49.9秒时6部电梯全部RESET+所有乘客都必须换乘才能到,hack到有人RTLE。
心得体会
线程安全
由于多线程的随机性,在Unit2时时常会出现评测机出了bug本地却无法复现,只能对着代码一一排除,找到问题所在。因此在处理多线程问题时要确定共享对象是谁,有哪些线程会对共享对象进行读写操作;之后再确定加锁的模块,保证线程之间的同步与互斥。还有就是要注意轮询是否产生、线程结束会不会出现异常。
层次化设计
本单元使用了生产者-消费者的架构,生产者为input线程,消费者为elevator线程,中间由schedule线程来控制。这个设计架构的好处是结构简单清晰,尤其是对于多线程问题,使用生产者-消费者架构后就可以省去大量的时间去思考如何确保线程安全,只需要在共享对象上加锁就可以了,符合高内聚、低耦合的设计原则。