OO第二单元作业总结

OO第二单元作业总结

一、总结分析三次作业中同步块的设置和锁的选择,并分析锁与同步块中处理语句直接的关系

1.第一次作业

​ 第一次作业只有一台电梯,所以我们只需要将输入的信息直接交给电梯即可。所以我们需要一个方法全部上锁的队列作为“盘子”,将输入放到盘子上,电梯从盘子上取出乘客请求。当然,在我们“放”和“取”的时候,我们也需要将这个队列上锁,以达到线程安全,如下图所示。

2.第二次作业

​ 第二次作业增加了多台电梯,所以我们直接将输入发送给电梯的思路就会产生一些问题。于是,调度器的作用就体现了出来。我们先将输入的队列(称作q1)交给调度器,再让调度器给各个电梯等待队列(称为qi)分配人员。这样,我们就需要在输入和调度器对q1上锁,并在调度器和对应电梯的队列qi上锁,就可以完成。同样,我们的队列依然是一个需要全部方法上锁的类。

3.第三次作业

​ 第三次作业增加了限制到达楼层,但并不影响锁的增减,所以架构实际与第二次作业完全相同,不需要新的锁。

4.锁与同步块中处理语句直接的关系

​ 他们都是将一部分区域上锁,当这部分被上锁后,别的线程如果想要使用这部分,那么就需要等待正在使用这部分的线程交出锁,所以他们上锁的方式是相似的。

​ 不同的是,同步块中是把这一部分的代码全部锁住,别的线程不能使用;而锁只会锁住一个对象,当别的线程使用这个对象时,就会不能使用。

二、总结分析三次作业中的调度器设计,并分析调度器如何与程序中的线程进行交互

1.第一次作业

​ 因为只有一台电梯,所以并没有使用到调度器。

2.第二次作业

​ 因为有多台电梯,调度器需要根据乘客请求选择适合的电梯,将乘客请求发给电梯。我选择了根据载客量调度的方法,即将乘客请求分配给当前未抵达目标层数人数最少的电梯。

3.第三次作业

​ 本次作业增加了到达层数限制。我先根据乘客请求,判断出其可以搭乘的电梯,然后再选择该类型中人数最少的电梯,将这个请求分配给这个电梯。

4.调度器如何与程序中的线程进行交互

​ 如(一)中所说,调度器需要与输入和电梯两类线程进行交互。与输入线程交互,只需要将等待队列共享即可,由输入线程将请求发送给调度器。与电梯交互时,需要先选出适合这个乘客请求的电梯,然后与这个电梯的等待队列交互,将请求添加到电梯的等待队列中即可。

三、从功能设计与性能设计的平衡方面,分析和总结自己第三次作业架构设计的可扩展性

1.架构总览

​ 第三次作业中,我延续了第二次作业中的生产者消费者模式的架构。我们看看类图。

​ UML协作图:

​ 简单来看,我们拥有三个线程,分别是Input类(用来输入请求)、Scheduler类(用来进行请求调度)和Elevator类(用来运行电梯)。Input与Scheduler共享WaitQueue1,用来保存乘客请求;Scheduler和多个Elevator共享WaitQueuei,用来将乘客请求分配给不同电梯;电梯内队列ElevatorQueue,实现了电梯内外队列的交互,并控制了电梯运行的目标楼层。我会把电梯内人的目标楼层作为高优先级,即以送人为主,接人为辅,能捎带就捎带的策略完成这个单元的作业。这就是第三次作业甚至本单元基本的架构。

2.从功能设计与性能设计的平衡方面分析

​ 在第二次作业对电梯运行优化后,我认为我的单个电梯的性能已经足够优秀了。主要的问题将会出现在调度上。

​ 在调度上,我设计了一个根据电梯乘客数量分配下一个请求的策略,这样的优势在于,它并不需要大量的代码和思考,策略比较简单,但是也可以完成相应的功能——调度起所有电梯运行。但是在性能上,这样的策略就稍显逊色了。由于我只是依据人数分配,所以分配的情况难以预测,可能会出现1-20和20-1这样的请求被分配到同一台电梯上。这时可能会造成别的电梯都在发呆,只有一台电梯还在忙碌的情况。这是第一个问题。

​ 第二个问题是,由于我担心换乘策略出问题,导致丢到正确分,所以我的第三次作业并没有考虑换乘,而是直接分配给了可以直达的电梯。所以这可能会导致当数据十分极端的时候,我只用到了一台电梯,最终超时。小心一刀六个! 但我为了避免拿不到正确分,最终选择了性能较差的不换乘电梯。

​ 综上考虑,我认为我的扩展性还是比较强的,因为无论是添加电梯还是更改限制层数,只需要对相应的方法进行修改,就可以快速完成,这也是为什么我第三次作业写得很快的原因。

3.使用SOLID原则分析
单一职责原则(SRP)

​ 我认为比较符合。我的每一个类都只是实现了自己的功能。

开放封闭原则(OCP)

​ 在Input中,如果我得到了添加电梯的请求,我会更改已生成的Scheduler类,这一点不太符合OCP,其它我认为还是比较符合的。

里氏替换原则(LSP)

​ 本次作业未使用继承。

接口隔离原则(ISP)

​ 本次作业未使用接口。

依赖倒置原则(DIP)

​ 看起来并没有产生这种现象。

四、分析自己的程序BUG

1.第一次作业

​ 由于刚开始接触多线程,开始难以理解轮询是什么,所以经常造成ctle。最后增加了wait-notifyall结构,便解决了这个问题。

2.第二次作业

​ 本次作业我更换了架构,于是出现了多种bug。其中一个是死锁。由于我在调度器类判断输入结束时直接return(如下图38-40行),导致电梯线程卡在wait中,没有被唤醒,最终造成死锁。在去掉return,合并到第54行的判断中后,这个bug便解决了。

​ 另一个问题是morning本身的问题。因为作业中说明morning模式两次输入之间不会超过2秒,所以我直接sleep了2秒,这在第一次作业中不会出现任何问题。但是在第二次作业中,因为有三个电梯,但我每次只会将一个请求分配给一个电梯,所以其他电梯接受两次输入之间的时间可能会大于2秒,导致bug。最后,我选择不再sleep,而是直接wait,解决了这个问题,如下图所示。

3.第三次作业

​ 由于上次的架构设计,这次更改很少,并没有产生bug。

4.互测和强测

​ 虽然并未出现bug,但是看到了第三次作业隔壁一刀六个的同学的测试点,内心十分害怕。还好我不是和他一组

​ 用他的代码跑了一下,确实造成了tle,原因是电梯不能换乘,这样就说明是设计的问题了,如上文所述,不换乘的策略性能太差了。所以这好像是不换乘不能解决的bug......

五、互测策略与他人BUG分析

1.互测策略

​ 继承了上次的兴趣,这次同样制作了评测机。

​ 评测机同样分为数据生成、重定向输入输出、结果检查三部分。

①数据生成部分

​ 在数据生成中,我们需要生成三类数据,分别是——三种模式,乘客请求和增加电梯请求。所以我们只需要逐个考虑即可。
三种模式的生成。对于三种模式,我为每个模式设置了一个概率(0-99),且概率和为100。这样我只需要随机生成一个0-99之间的整数,通过判断这个数所在区间,即可完成模式的生成。后文的依据概率生成与之同理。
​ 增加电梯请求的生成。电梯请求可以看为[time]ADD-elevator-type。其中time为时间戳,elevator为增加电梯编号,type为类型。我们只需要对着三者分别生成即可,但要注意已经生成过得电梯编号不能再次出现。
乘客请求的生成。乘客请求可以看为[time]id-FROM-x-TO-y,其中time为时间戳,id为乘客编号,x为出发层,y为目标层。我们只需要对这四处分别生成即可,但是也需要增加一些限制。

②重定向输入输出部分

​ 我们需要将数据生成部分所生成的数据输入到作业的程序中,再将程序的输出返还给结果检查部分。于是我们可以使用python中的popen()方法建立管道,进行重定向。

③结果检查部分

​ 首先我们考虑输出。输出一共有五种类型,分别是in,out,open,close,arrive。
​ 我们想要收集输出的信息,就需要分辨出这五种类型并做相应处理。由于它们的格式具有共性,在这里,我们可以使用正则表达式判断。
​ 获取信息被我们解决之后,我们要考虑的是怎样检查正确性。我们可以将结果看为一个状态机,每获取一条输出,我们就更改这个状态机的状态,最后,我们通过状态机所处状态来判断结果是否正确。状态转移图可以简化为下图所示。当然,我们还需要更多的信息来调整状态机。

2.线程安全相关的问题

​ 由于有评测机,我们就可以使用它跑很多很多组数据,只要有线程安全相关的问题,那评测机一定可以测出来,尽管概率很小。在互测时,我发现了一位同学有关线程安全的问题,但是复现率很低很低,就是说,可能需要跑很多很多次,才会出现这样的问题,所以最后我并没有选择hack他。

3.与第一单元测试策略的差异

​ 由于这次的输出可能有多种可能,所以我们并不好直接判断输出是否有误,因此输出检查部分的制作显得比较麻烦。但是我本单元测试的策略其实与第一单元类似,都是使用大量的、覆盖性强的数据进行全面测试,如果没有出现问题,我就可以认为程序正确。

六、心得体会

​ 通过本单元的学习,我对多线程有了更深的理解。在本单元结束后,我对生产者消费者模式的应用也更加的熟练了。对于死锁、线程不安全,我现在可以依据代码分析出是否会出现这种情况以及为什么会出现这种情况。

​ 对于线程安全,一定要注意共享的对象会不会被某个写,如果有的话,需要在这些线程中对这个对象上锁,同时考虑是否要增加wait-notifyall架构。

​ 对于层次架构,我认为生产者消费者模式是很好的一个架构,可以使用很久。不过我们需要增加一个调度器线程,来实现一对多的功能。

​ 总的来说,我觉得这单元想要做到全对其实并不难,只要使用生产者消费者模式,应该可以写出一个很好的架构,但是困难的应该是在各种策略中选择最优最好的策略,这需要下很大的功夫。

​ 不过,在这个单元让我同样收获很大的还有评测机的制作,并且让我锻炼了合作的能力。这单元的评测机代码量十分大,对于之前python零基础的我,我花费了大量的时间在学习python语法中。因为之前正好也打算学习python,所以制作评测机的过程并不是很枯燥,最终,我的评测机写得也很面向对象(小声)。

​ 同样,由于我和我的同学一起完成了这次的评测机,所以也让我学会了一些合作的“技巧”。

《合作》
posted @ 2021-04-23 10:14  Eddie555  阅读(82)  评论(0)    收藏  举报