一、线程安全相关
1.第五次作业
由于只有单部电梯,因此我没有使用调度器,直接让电梯和输入线程共享请求队列。我将二者对它的写入操作加锁,就保证了线程安全。
2.第六次作业
在加入了多部电梯之后,我在第五次电梯的基础上额外补充了一个调度器和一个主请求队列,同时每一步电梯享有一个单独的请求队列。这样一来,每一个请求队列都仅有两个线程进行读写操作,因此加锁之后就能够保证线程安全。
3.第七次作业
本次作业几乎与第六次作业相同,唯一不同的是电梯可能会给主请求队列进行写入操作,因此整体上与第六次作业相同。我将请求队列构造为线程安全类,通过加锁的方式保证线程安全,其余部分与第六次作业几乎完全相同。
二、调度器相关
1.调度器设计
我整体上采取的是分布式调度,即调度器接收到请求后分发到每一个电梯的独立请求队列,在调度器中完成请求的分配。三次作业中电梯的运行方式几乎完全相同,即按照指定的请求队列进行运行。
对于不同的模式,调度器也会采取不同的策略。调度器通过将电梯当前状况和请求的起点终点两个条件综合分析,得到对每一个请求的分配方式,并将请求加入到对应电梯的独立请求队列中。
2.与线程的交互
调度器同时与输入线程和电梯线程进行交互,在收到请求之后与电梯进行交互,将请求分配给电梯;在最后一次作业中还存在电梯将请求写入主请求队列的反向交互。整体上线程之间两两通信,结构比较清晰,处理起来很方便。
三、第三次作业架构设计
1.UML类图
2.UML时序图
3.可扩展性
从第三次的架构上来看,我的代码非常常规、非常普通,但由于各个类之间的解耦做的还算不错(我认为),因此加入新的需求的时候应该也能很方便的完成扩展。但如果新的需求超出了现有的设计思路,可能也还是需要花一番功夫。
四、分析bug
我仅在第二次作业中出现了bug:在第二次作业中,我的random模式出现了灾难性的bug,由于我将morning模式的处理方式复制粘贴到random模式的过程中,不慎将“选择电梯——分配”这样一个代码块中的“分配”行为放到了“选择电梯”的循环中,导致所有的请求没有进行分配策略,都被直接分配给了一号电梯,因此导致了多个random测试点的超时。这也让我意识到ctrl+cv的危害,即容易出现许多意料之外的bug,今后写代码的过程中也应该尽量避免这样的行为。
五、hack策略
1.测试策略
在这三次互测中,我没能hack到人,但我学习到了一种可行的hack方式——以第三次作业为例,我可以通过手动特殊构造样例来达到hack的效果。具体方式为:我可以通过计算,将一个最优策略下能通过测试但大部分调度策略无法通过的测试点投入hack中。这时候就有一个问题了,我自己的电梯显然也不能在70s内跑不完。我们可以手动生成一个输出结果,只要保证是正常的运行情况,就能够通过互测的数据点测试。在互测结果出来之前,我不认为这是一种合乎规定的hack方式;但事实上,我所在的互测屋内,有人通过这个方式一次hack了六个人(除了我)。这给我带来了启发,加深了我对hack的理解,让人耳目一新。
2.处理线程安全问题的策略
由于我在设计时,尽量避免两个以上的线程对同一个对象进行操作,因此整体上线程安全的问题并不明显;同时,我将请求队列设计为线程相对安全的类,从根本上解决了可能出现的线程安全问题。
3.与第一单元差异
首先是结果的不可预测性,也就是每个人的程序输出都不一样,想要完全准确测试是一件很困难的事情。因此,测试的策略更倾向于检测程序运行的稳定性,可以通过运行时间、乘客到达数量等进行判断来实现正确性的检测。其次,由于测试的结果往往不可复现,因此debug也不像单线程程序那样轻松,需要对程序进行根本上的分析才能找出问题所在。因此,整体上来看,相比于第一单元,本单元作业具有更高的难度。
六、心得体会
1.线程安全方面
和我一开始预想的不一样,线程安全的问题并没有给我带来太多的困扰,或许是因为我自始至终都以设计简明为原则,尽可能避免了线程安全的问题。我的心得体会,即尽可能避免容易出现问题的设计方式,大道至简嘛。
2.层次化设计方面
整体上,我认为我这个单元的架构还算不错。经过了第一单元的折磨,第二单元的一开始我就以可扩展性为核心思想。在这三次作业中,我并没有出现被迫重构的情况,更多的是在原有架构上进行内容的填充,因此整体上也相对轻松。这也再次提醒我架构的重要性,我希望今后的作业中我都能保持当前的架构状态,不重构就算胜利!~